import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.issue.IssueUpdater;
-import org.sonar.core.issue.workflow.IssueWorkflow;
+import org.sonar.server.issue.workflow.IssueWorkflow;
import org.sonar.core.util.Uuids;
import org.sonar.server.computation.analysis.AnalysisMetadataHolder;
import org.sonar.core.issue.DefaultActionPlan;
import org.sonar.core.issue.DefaultIssueComment;
import org.sonar.core.issue.FieldDiffs;
-import org.sonar.core.issue.workflow.Transition;
+import org.sonar.server.issue.workflow.Transition;
import org.sonar.db.component.ResourceDao;
import org.sonar.db.component.ResourceDto;
import org.sonar.db.component.ResourceQuery;
import org.sonar.core.issue.DefaultIssueBuilder;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.issue.IssueUpdater;
-import org.sonar.core.issue.workflow.IssueWorkflow;
-import org.sonar.core.issue.workflow.Transition;
+import org.sonar.server.issue.workflow.IssueWorkflow;
+import org.sonar.server.issue.workflow.Transition;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
import org.sonar.api.issue.Issue;
import org.sonar.api.server.ServerSide;
import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.workflow.IssueWorkflow;
-import org.sonar.core.issue.workflow.Transition;
+import org.sonar.server.issue.workflow.IssueWorkflow;
+import org.sonar.server.issue.workflow.Transition;
import org.sonar.server.user.UserSession;
@ServerSide
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import javax.annotation.Nullable;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.user.User;
+
+interface Function {
+ interface Context {
+ Issue issue();
+
+ Context setAssignee(@Nullable User user);
+
+ Context setResolution(@Nullable String s);
+
+ Context setCloseDate(boolean b);
+
+ Context setLine(@Nullable Integer line);
+ }
+
+ void execute(Context context);
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import javax.annotation.Nullable;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.user.User;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
+
+@ServerSide
+public class FunctionExecutor {
+
+ private final IssueUpdater updater;
+
+ public FunctionExecutor(IssueUpdater updater) {
+ this.updater = updater;
+ }
+
+ public void execute(Function[] functions, DefaultIssue issue, IssueChangeContext changeContext) {
+ if (functions.length > 0) {
+ FunctionContext functionContext = new FunctionContext(updater, issue, changeContext);
+ for (Function function : functions) {
+ function.execute(functionContext);
+ }
+ }
+ }
+
+ static class FunctionContext implements Function.Context {
+ private final IssueUpdater updater;
+ private final DefaultIssue issue;
+ private final IssueChangeContext changeContext;
+
+ FunctionContext(IssueUpdater updater, DefaultIssue issue, IssueChangeContext changeContext) {
+ this.updater = updater;
+ this.issue = issue;
+ this.changeContext = changeContext;
+ }
+
+ @Override
+ public Issue issue() {
+ return issue;
+ }
+
+ @Override
+ public Function.Context setAssignee(@Nullable User user) {
+ updater.assign(issue, user, changeContext);
+ return this;
+ }
+
+ @Override
+ public Function.Context setResolution(@Nullable String s) {
+ updater.setResolution(issue, s, changeContext);
+ return this;
+ }
+
+ @Override
+ public Function.Context setCloseDate(boolean b) {
+ updater.setCloseDate(issue, b ? changeContext.date() : null, changeContext);
+ return this;
+ }
+
+ @Override
+ public Function.Context setLine(@Nullable Integer line) {
+ updater.setLine(issue, line);
+ return this;
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.condition.Condition;
+import org.sonar.core.issue.DefaultIssue;
+
+enum IsBeingClosed implements Condition {
+ INSTANCE;
+
+ @Override
+ public boolean matches(Issue issue) {
+ return ((DefaultIssue) issue).isBeingClosed();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.condition.Condition;
+
+enum IsManual implements Condition {
+ INSTANCE;
+
+ @Override
+ public boolean matches(Issue issue) {
+ return issue.ruleKey().isManual();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import java.util.List;
+import org.picocontainer.Startable;
+import org.sonar.api.issue.DefaultTransitions;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.condition.HasResolution;
+import org.sonar.api.issue.condition.NotCondition;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
+
+@ServerSide
+public class IssueWorkflow implements Startable {
+
+ private final FunctionExecutor functionExecutor;
+ private final IssueUpdater updater;
+ private StateMachine machine;
+
+ public IssueWorkflow(FunctionExecutor functionExecutor, IssueUpdater updater) {
+ this.functionExecutor = functionExecutor;
+ this.updater = updater;
+ }
+
+ @Override
+ public void start() {
+ StateMachine.Builder builder = StateMachine.builder()
+ // order is important for UI
+ .states(Issue.STATUS_OPEN, Issue.STATUS_CONFIRMED, Issue.STATUS_REOPENED, Issue.STATUS_RESOLVED, Issue.STATUS_CLOSED);
+
+ buildManualTransitions(builder);
+ buildAutomaticTransitions(builder);
+ machine = builder.build();
+ }
+
+ private void buildManualTransitions(StateMachine.Builder builder) {
+ builder.transition(Transition.builder(DefaultTransitions.CONFIRM)
+ .from(Issue.STATUS_OPEN).to(Issue.STATUS_CONFIRMED)
+ .functions(new SetResolution(null))
+ .build())
+ .transition(Transition.builder(DefaultTransitions.CONFIRM)
+ .from(Issue.STATUS_REOPENED).to(Issue.STATUS_CONFIRMED)
+ .functions(new SetResolution(null))
+ .build())
+ .transition(Transition.builder(DefaultTransitions.UNCONFIRM)
+ .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_REOPENED)
+ .functions(new SetResolution(null))
+ .build())
+ .transition(Transition.builder(DefaultTransitions.RESOLVE)
+ .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
+ .functions(new SetResolution(Issue.RESOLUTION_FIXED))
+ .build())
+ .transition(Transition.builder(DefaultTransitions.RESOLVE)
+ .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED)
+ .functions(new SetResolution(Issue.RESOLUTION_FIXED))
+ .build())
+ .transition(Transition.builder(DefaultTransitions.RESOLVE)
+ .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED)
+ .functions(new SetResolution(Issue.RESOLUTION_FIXED))
+ .build())
+ .transition(Transition.builder(DefaultTransitions.REOPEN)
+ .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED)
+ .functions(new SetResolution(null))
+ .build())
+ .transition(Transition.builder(DefaultTransitions.REOPEN)
+ .conditions(IsManual.INSTANCE)
+ .from(Issue.STATUS_CLOSED).to(Issue.STATUS_REOPENED)
+ .functions(new SetResolution(null), new SetCloseDate(false))
+ .build())
+
+ // resolve as false-positive
+ .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
+ .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
+ .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
+ .requiredProjectPermission(UserRole.ISSUE_ADMIN)
+ .build())
+ .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
+ .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED)
+ .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
+ .requiredProjectPermission(UserRole.ISSUE_ADMIN)
+ .build())
+ .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
+ .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED)
+ .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
+ .requiredProjectPermission(UserRole.ISSUE_ADMIN)
+ .build())
+
+ // resolve as won't fix
+ .transition(Transition.builder(DefaultTransitions.WONT_FIX)
+ .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
+ .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
+ .requiredProjectPermission(UserRole.ISSUE_ADMIN)
+ .build())
+ .transition(Transition.builder(DefaultTransitions.WONT_FIX)
+ .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED)
+ .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
+ .requiredProjectPermission(UserRole.ISSUE_ADMIN)
+ .build())
+ .transition(Transition.builder(DefaultTransitions.WONT_FIX)
+ .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED)
+ .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
+ .requiredProjectPermission(UserRole.ISSUE_ADMIN)
+ .build());
+
+ }
+
+ private void buildAutomaticTransitions(StateMachine.Builder builder) {
+ // Close the "end of life" issues (disabled/deleted rule, deleted component)
+ builder
+ .transition(Transition.builder("automaticclose")
+ .from(Issue.STATUS_OPEN).to(Issue.STATUS_CLOSED)
+ .conditions(IsBeingClosed.INSTANCE)
+ .functions(SetClosed.INSTANCE, new SetCloseDate(true))
+ .automatic()
+ .build())
+ .transition(Transition.builder("automaticclose")
+ .from(Issue.STATUS_REOPENED).to(Issue.STATUS_CLOSED)
+ .conditions(IsBeingClosed.INSTANCE)
+ .functions(SetClosed.INSTANCE, new SetCloseDate(true))
+ .automatic()
+ .build())
+ .transition(Transition.builder("automaticclose")
+ .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_CLOSED)
+ .conditions(IsBeingClosed.INSTANCE)
+ .functions(SetClosed.INSTANCE, new SetCloseDate(true))
+ .automatic()
+ .build())
+ .transition(Transition.builder("automaticclose")
+ .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_CLOSED)
+ .conditions(new OrCondition(IsBeingClosed.INSTANCE, IsManual.INSTANCE))
+ .functions(SetClosed.INSTANCE, new SetCloseDate(true))
+ .automatic()
+ .build())
+
+ // Reopen issues that are marked as resolved but that are still alive.
+ // Manual issues are kept resolved.
+ .transition(Transition.builder("automaticreopen")
+ .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED)
+ .conditions(new NotCondition(IsBeingClosed.INSTANCE), new HasResolution(Issue.RESOLUTION_FIXED), new NotCondition(IsManual.INSTANCE))
+ .functions(new SetResolution(null), new SetCloseDate(false))
+ .automatic()
+ .build());
+ }
+
+ @Override
+ public void stop() {
+ // nothing to do
+ }
+
+ public boolean doTransition(DefaultIssue issue, String transitionKey, IssueChangeContext issueChangeContext) {
+ Transition transition = stateOf(issue).transition(transitionKey);
+ if (transition != null && !transition.automatic()) {
+ functionExecutor.execute(transition.functions(), issue, issueChangeContext);
+ updater.setStatus(issue, transition.to(), issueChangeContext);
+ return true;
+ }
+ return false;
+ }
+
+ public List<Transition> outTransitions(Issue issue) {
+ State state = machine.state(issue.status());
+ if (state == null) {
+ throw new IllegalArgumentException("Unknown status: " + issue.status());
+ }
+ return state.outManualTransitions(issue);
+ }
+
+ public void doAutomaticTransition(DefaultIssue issue, IssueChangeContext issueChangeContext) {
+ Transition transition = stateOf(issue).outAutomaticTransition(issue);
+ if (transition != null) {
+ functionExecutor.execute(transition.functions(), issue, issueChangeContext);
+ updater.setStatus(issue, transition.to(), issueChangeContext);
+ }
+ }
+
+ public List<String> statusKeys() {
+ return machine.stateKeys();
+ }
+
+ private State stateOf(DefaultIssue issue) {
+ State state = machine.state(issue.status());
+ if (state == null) {
+ throw new IllegalStateException("Unknown status: " + issue.status() + " [issue=" + issue.key() + "]");
+ }
+ return state;
+ }
+
+ StateMachine machine() {
+ return machine;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.condition.Condition;
+
+public class OrCondition implements Condition {
+ private final Condition[] conditions;
+
+ public OrCondition(Condition... conditions) {
+ this.conditions = conditions;
+ }
+
+ @Override
+ public boolean matches(Issue issue) {
+ for (Condition condition : conditions) {
+ if (condition.matches(issue)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+class SetCloseDate implements Function {
+ private final boolean set;
+
+ public SetCloseDate(boolean set) {
+ this.set = set;
+ }
+
+ @Override
+ public void execute(Context context) {
+ context.setCloseDate(set);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import org.sonar.api.issue.Issue;
+import org.sonar.core.issue.DefaultIssue;
+
+public enum SetClosed implements Function {
+ INSTANCE;
+
+ @Override
+ public void execute(Context context) {
+ DefaultIssue issue = (DefaultIssue) context.issue();
+ if (issue.isOnDisabledRule()) {
+ context.setResolution(Issue.RESOLUTION_REMOVED);
+ } else {
+ context.setResolution(Issue.RESOLUTION_FIXED);
+ }
+
+ // closed issues are not "tracked" -> the line number does not evolve anymore
+ // when code changes. That's misleading for end-users, so line number
+ // is unset.
+ context.setLine(null);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import javax.annotation.Nullable;
+
+public class SetResolution implements Function {
+ private final String resolution;
+
+ public SetResolution(@Nullable String resolution) {
+ this.resolution = resolution;
+ }
+
+ @Override
+ public void execute(Context context) {
+ context.setResolution(resolution);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.issue.Issue;
+
+public class State {
+ private final String key;
+ private final Transition[] outTransitions;
+
+ public State(String key, Transition[] outTransitions) {
+ Preconditions.checkArgument(!Strings.isNullOrEmpty(key), "State key must be set");
+ Preconditions.checkArgument(StringUtils.isAllUpperCase(key), "State key must be upper-case");
+ checkDuplications(outTransitions, key);
+
+ this.key = key;
+ this.outTransitions = outTransitions;
+ }
+
+ private static void checkDuplications(Transition[] transitions, String stateKey) {
+ Set<String> keys = Sets.newHashSet();
+ for (Transition transition : transitions) {
+ if (keys.contains(transition.key())) {
+ throw new IllegalArgumentException("Transition '" + transition.key() +
+ "' is declared several times from the originating state '" + stateKey + "'");
+ }
+ keys.add(transition.key());
+ }
+ }
+
+ public List<Transition> outManualTransitions(Issue issue) {
+ List<Transition> result = Lists.newArrayList();
+ for (Transition transition : outTransitions) {
+ if (!transition.automatic() && transition.supports(issue)) {
+ result.add(transition);
+ }
+ }
+ return result;
+ }
+
+ @CheckForNull
+ public Transition outAutomaticTransition(Issue issue) {
+ Transition result = null;
+ for (Transition transition : outTransitions) {
+ if (transition.automatic() && transition.supports(issue)) {
+ if (result == null) {
+ result = transition;
+ } else {
+ throw new IllegalStateException("Several automatic transitions are available for issue: " + issue);
+ }
+ }
+ }
+ return result;
+ }
+
+ Transition transition(String transitionKey) {
+ for (Transition transition : outTransitions) {
+ if (transitionKey.equals(transition.key())) {
+ return transition;
+ }
+ }
+ throw new IllegalStateException("Transition from state " + key + " does not exist: " + transitionKey);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Sets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+
+public class StateMachine {
+
+ private final List<String> keys;
+ private final Map<String, State> byKey;
+
+ private StateMachine(Builder builder) {
+ this.keys = ImmutableList.copyOf(builder.states);
+ ImmutableMap.Builder<String, State> mapBuilder = ImmutableMap.builder();
+ for (String stateKey : builder.states) {
+ List<Transition> outTransitions = builder.outTransitions.get(stateKey);
+ State state = new State(stateKey, outTransitions.toArray(new Transition[outTransitions.size()]));
+ mapBuilder.put(stateKey, state);
+ }
+ byKey = mapBuilder.build();
+ }
+
+ @CheckForNull
+ public State state(String stateKey) {
+ return byKey.get(stateKey);
+ }
+
+ public List<String> stateKeys() {
+ return keys;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private final Set<String> states = Sets.newLinkedHashSet();
+ // transitions per originating state
+ private final ListMultimap<String, Transition> outTransitions = ArrayListMultimap.create();
+
+ private Builder() {
+ }
+
+ public Builder states(String... keys) {
+ states.addAll(Arrays.asList(keys));
+ return this;
+ }
+
+ public Builder transition(Transition transition) {
+ Preconditions.checkArgument(states.contains(transition.from()), "Originating state does not exist: " + transition.from());
+ Preconditions.checkArgument(states.contains(transition.to()), "Destination state does not exist: " + transition.to());
+ outTransitions.put(transition.from(), transition);
+ return this;
+ }
+
+ public StateMachine build() {
+ Preconditions.checkArgument(!states.isEmpty(), "At least one state is required");
+ return new StateMachine(this);
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.condition.Condition;
+
+public class Transition {
+ private final String key;
+ private final String from;
+ private final String to;
+ private final Condition[] conditions;
+ private final Function[] functions;
+ private final boolean automatic;
+ private String requiredProjectPermission;
+
+ private Transition(TransitionBuilder builder) {
+ key = builder.key;
+ from = builder.from;
+ to = builder.to;
+ conditions = builder.conditions.toArray(new Condition[builder.conditions.size()]);
+ functions = builder.functions.toArray(new Function[builder.functions.size()]);
+ automatic = builder.automatic;
+ requiredProjectPermission = builder.requiredProjectPermission;
+ }
+
+ public String key() {
+ return key;
+ }
+
+ String from() {
+ return from;
+ }
+
+ String to() {
+ return to;
+ }
+
+ Condition[] conditions() {
+ return conditions;
+ }
+
+ Function[] functions() {
+ return functions;
+ }
+
+ boolean automatic() {
+ return automatic;
+ }
+
+ public boolean supports(Issue issue) {
+ for (Condition condition : conditions) {
+ if (!condition.matches(issue)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public String requiredProjectPermission() {
+ return requiredProjectPermission;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Transition that = (Transition) o;
+ if (!from.equals(that.from)) {
+ return false;
+ }
+ if (!key.equals(that.key)) {
+ return false;
+ }
+ return to.equals(that.to);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = key.hashCode();
+ result = 31 * result + from.hashCode();
+ result = 31 * result + to.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s->%s->%s", from, key, to);
+ }
+
+ public static Transition create(String key, String from, String to) {
+ return builder(key).from(from).to(to).build();
+ }
+
+ public static TransitionBuilder builder(String key) {
+ return new TransitionBuilder(key);
+ }
+
+ public static class TransitionBuilder {
+ private final String key;
+ private String from;
+ private String to;
+ private List<Condition> conditions = Lists.newArrayList();
+ private List<Function> functions = Lists.newArrayList();
+ private boolean automatic = false;
+ private String requiredProjectPermission;
+
+ private TransitionBuilder(String key) {
+ this.key = key;
+ }
+
+ public TransitionBuilder from(String from) {
+ this.from = from;
+ return this;
+ }
+
+ public TransitionBuilder to(String to) {
+ this.to = to;
+ return this;
+ }
+
+ public TransitionBuilder conditions(Condition... c) {
+ this.conditions.addAll(Arrays.asList(c));
+ return this;
+ }
+
+ public TransitionBuilder functions(Function... f) {
+ this.functions.addAll(Arrays.asList(f));
+ return this;
+ }
+
+ public TransitionBuilder automatic() {
+ this.automatic = true;
+ return this;
+ }
+
+ public TransitionBuilder requiredProjectPermission(String requiredProjectPermission) {
+ this.requiredProjectPermission = requiredProjectPermission;
+ return this;
+ }
+
+ public Transition build() {
+ Preconditions.checkArgument(!Strings.isNullOrEmpty(key), "Transition key must be set");
+ Preconditions.checkArgument(StringUtils.isAllLowerCase(key), "Transition key must be lower-case");
+ Preconditions.checkArgument(!Strings.isNullOrEmpty(from), "Originating status must be set");
+ Preconditions.checkArgument(!Strings.isNullOrEmpty(to), "Destination status must be set");
+ return new Transition(this);
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+enum UnsetAssignee implements Function {
+ INSTANCE;
+
+ @Override
+ public void execute(Context context) {
+ context.setAssignee(null);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.issue.workflow;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
import org.sonar.api.issue.Issue;
import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.text.JsonWriter;
-import org.sonar.core.issue.workflow.Transition;
+import org.sonar.server.issue.workflow.Transition;
import org.sonar.server.issue.ActionService;
import org.sonar.server.issue.IssueService;
import org.sonar.server.user.UserSession;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
-import org.sonar.core.issue.workflow.Transition;
+import org.sonar.server.issue.workflow.Transition;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.issue.ActionPlanDto;
import org.sonar.db.issue.IssueChangeDto;
import org.sonar.api.utils.Duration;
import org.sonar.api.utils.Durations;
import org.sonar.api.utils.Paging;
-import org.sonar.core.issue.workflow.Transition;
+import org.sonar.server.issue.workflow.Transition;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.issue.ActionPlanDto;
import org.sonar.db.issue.IssueChangeDto;
import org.sonar.api.server.rule.RulesDefinitionXmlLoader;
import org.sonar.core.component.DefaultResourceTypes;
import org.sonar.core.issue.IssueUpdater;
-import org.sonar.core.issue.workflow.FunctionExecutor;
-import org.sonar.core.issue.workflow.IssueWorkflow;
+import org.sonar.server.issue.workflow.FunctionExecutor;
+import org.sonar.server.issue.workflow.IssueWorkflow;
import org.sonar.core.timemachine.Periods;
import org.sonar.core.user.DefaultUserFinder;
import org.sonar.core.user.DeprecatedUserFinder;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.issue.IssueUpdater;
-import org.sonar.core.issue.workflow.IssueWorkflow;
+import org.sonar.server.issue.workflow.IssueWorkflow;
import org.sonar.db.protobuf.DbCommons;
import org.sonar.db.protobuf.DbIssues;
import org.sonar.api.rule.Severity;
import org.sonar.api.security.DefaultGroups;
import org.sonar.api.web.UserRole;
-import org.sonar.core.issue.workflow.Transition;
+import org.sonar.server.issue.workflow.Transition;
import org.sonar.core.permission.GlobalPermissions;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.api.issue.Issue;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
-import org.sonar.core.issue.workflow.IssueWorkflow;
-import org.sonar.core.issue.workflow.Transition;
+import org.sonar.server.issue.workflow.IssueWorkflow;
+import org.sonar.server.issue.workflow.Transition;
import org.sonar.server.tester.UserSessionRule;
import static com.google.common.collect.Lists.newArrayList;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import org.junit.Test;
+import org.sonar.core.issue.DefaultIssue;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.issue.workflow.IsBeingClosed.INSTANCE;
+
+public class IsBeingClosedTest {
+
+ @Test
+ public void should_be_end_of_life() {
+ DefaultIssue issue = new DefaultIssue();
+ assertThat(INSTANCE.matches(issue.setBeingClosed(true))).isTrue();
+ assertThat(INSTANCE.matches(issue.setBeingClosed(false))).isFalse();
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.core.issue.DefaultIssue;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.issue.workflow.IsManual.INSTANCE;
+
+public class IsManualTest {
+
+ @Test
+ public void should_match() {
+ DefaultIssue issue = new DefaultIssue();
+ assertThat(INSTANCE.matches(issue.setRuleKey(RuleKey.of(RuleKey.MANUAL_REPOSITORY_KEY, "R1")))).isTrue();
+ assertThat(INSTANCE.matches(issue.setRuleKey(RuleKey.of("java", "R1")))).isFalse();
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.time.DateUtils;
+import org.junit.Test;
+import org.sonar.api.issue.DefaultTransitions;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
+import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
+import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX;
+import static org.sonar.api.issue.Issue.STATUS_CLOSED;
+import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
+import static org.sonar.api.issue.Issue.STATUS_OPEN;
+import static org.sonar.api.issue.Issue.STATUS_REOPENED;
+import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
+import static org.sonar.api.rule.RuleKey.MANUAL_REPOSITORY_KEY;
+
+public class IssueWorkflowTest {
+
+ IssueUpdater updater = new IssueUpdater();
+ IssueWorkflow workflow = new IssueWorkflow(new FunctionExecutor(updater), updater);
+
+ @Test
+ public void init_state_machine() {
+ assertThat(workflow.machine()).isNull();
+ workflow.start();
+ assertThat(workflow.machine()).isNotNull();
+ assertThat(workflow.machine().state(STATUS_OPEN)).isNotNull();
+ assertThat(workflow.machine().state(STATUS_CONFIRMED)).isNotNull();
+ assertThat(workflow.machine().state(STATUS_CLOSED)).isNotNull();
+ assertThat(workflow.machine().state(STATUS_REOPENED)).isNotNull();
+ assertThat(workflow.machine().state(STATUS_RESOLVED)).isNotNull();
+ workflow.stop();
+ }
+
+ @Test
+ public void list_statuses() {
+ workflow.start();
+ // order is important for UI
+ assertThat(workflow.statusKeys()).containsSequence(STATUS_OPEN, STATUS_CONFIRMED, STATUS_REOPENED, STATUS_RESOLVED, STATUS_CLOSED);
+ }
+
+ @Test
+ public void list_out_transitions_from_status_open() {
+ workflow.start();
+
+ DefaultIssue issue = new DefaultIssue().setStatus(STATUS_OPEN);
+ List<Transition> transitions = workflow.outTransitions(issue);
+ assertThat(keys(transitions)).containsOnly("confirm", "falsepositive", "resolve", "wontfix");
+ }
+
+ @Test
+ public void list_out_transitions_from_status_confirmed() {
+ workflow.start();
+
+ DefaultIssue issue = new DefaultIssue().setStatus(STATUS_CONFIRMED);
+ List<Transition> transitions = workflow.outTransitions(issue);
+ assertThat(keys(transitions)).containsOnly("unconfirm", "falsepositive", "resolve", "wontfix");
+ }
+
+ @Test
+ public void list_out_transitions_from_status_resolved() {
+ workflow.start();
+
+ DefaultIssue issue = new DefaultIssue().setStatus(STATUS_RESOLVED);
+ List<Transition> transitions = workflow.outTransitions(issue);
+ assertThat(keys(transitions)).containsOnly("reopen");
+ }
+
+ @Test
+ public void list_out_transitions_from_status_reopen() {
+ workflow.start();
+
+ DefaultIssue issue = new DefaultIssue().setStatus(STATUS_REOPENED);
+ List<Transition> transitions = workflow.outTransitions(issue);
+ assertThat(keys(transitions)).containsOnly("confirm", "resolve", "falsepositive", "wontfix");
+ }
+
+ @Test
+ public void list_no_out_transition_from_status_closed() {
+ workflow.start();
+
+ DefaultIssue issue = new DefaultIssue().setStatus(STATUS_CLOSED).setRuleKey(RuleKey.of("java", "R1 "));
+ List<Transition> transitions = workflow.outTransitions(issue);
+ assertThat(transitions).isEmpty();
+ }
+
+ @Test
+ public void list_out_transitions_from_status_closed_on_manual_issue() {
+ workflow.start();
+
+ // Manual issue because of reporter
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("ABCDE")
+ .setStatus(STATUS_CLOSED)
+ .setRuleKey(RuleKey.of("manual", "Performance"))
+ .setReporter("simon");
+
+ List<Transition> transitions = workflow.outTransitions(issue);
+ assertThat(keys(transitions)).containsOnly("reopen");
+ }
+
+ @Test
+ public void fail_if_unknown_status_when_listing_transitions() {
+ workflow.start();
+
+ DefaultIssue issue = new DefaultIssue().setStatus("xxx");
+ try {
+ workflow.outTransitions(issue);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessage("Unknown status: xxx");
+ }
+ }
+
+ @Test
+ public void automatically_close_resolved_issue() {
+ workflow.start();
+
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("ABCDE")
+ .setRuleKey(RuleKey.of("js", "S001"))
+ .setResolution(RESOLUTION_FIXED)
+ .setStatus(STATUS_RESOLVED)
+ .setNew(false)
+ .setBeingClosed(true);
+ Date now = new Date();
+ workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
+ assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
+ assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
+ assertThat(issue.closeDate()).isNotNull();
+ assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
+ }
+
+ @Test
+ public void close_open_dead_issue() {
+ workflow.start();
+
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("ABCDE")
+ .setResolution(null)
+ .setStatus(STATUS_OPEN)
+ .setNew(false)
+ .setBeingClosed(true);
+ Date now = new Date();
+ workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
+ assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
+ assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
+ assertThat(issue.closeDate()).isNotNull();
+ assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
+ }
+
+ @Test
+ public void close_reopened_dead_issue() {
+ workflow.start();
+
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("ABCDE")
+ .setResolution(null)
+ .setStatus(STATUS_REOPENED)
+ .setNew(false)
+ .setBeingClosed(true);
+ Date now = new Date();
+ workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
+ assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
+ assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
+ assertThat(issue.closeDate()).isNotNull();
+ assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
+ }
+
+ @Test
+ public void close_confirmed_dead_issue() {
+ workflow.start();
+
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("ABCDE")
+ .setResolution(null)
+ .setStatus(STATUS_CONFIRMED)
+ .setNew(false)
+ .setBeingClosed(true);
+ Date now = new Date();
+ workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
+ assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
+ assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
+ assertThat(issue.closeDate()).isNotNull();
+ assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
+ }
+
+ @Test
+ public void fail_if_unknown_status_on_automatic_trans() {
+ workflow.start();
+
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("ABCDE")
+ .setResolution(RESOLUTION_FIXED)
+ .setStatus("xxx")
+ .setNew(false)
+ .setBeingClosed(true);
+ try {
+ workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(new Date()));
+ fail();
+ } catch (IllegalStateException e) {
+ assertThat(e).hasMessage("Unknown status: xxx [issue=ABCDE]");
+ }
+ }
+
+ @Test
+ public void flag_as_false_positive() {
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("ABCDE")
+ .setStatus(STATUS_OPEN)
+ .setRuleKey(RuleKey.of("squid", "AvoidCycle"))
+ .setAssignee("morgan");
+
+ workflow.start();
+ workflow.doTransition(issue, DefaultTransitions.FALSE_POSITIVE, IssueChangeContext.createScan(new Date()));
+
+ assertThat(issue.resolution()).isEqualTo(RESOLUTION_FALSE_POSITIVE);
+ assertThat(issue.status()).isEqualTo(STATUS_RESOLVED);
+
+ // should remove assignee
+ assertThat(issue.assignee()).isNull();
+ }
+
+ @Test
+ public void wont_fix() {
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("ABCDE")
+ .setStatus(STATUS_OPEN)
+ .setRuleKey(RuleKey.of("squid", "AvoidCycle"))
+ .setAssignee("morgan");
+
+ workflow.start();
+ workflow.doTransition(issue, DefaultTransitions.WONT_FIX, IssueChangeContext.createScan(new Date()));
+
+ assertThat(issue.resolution()).isEqualTo(RESOLUTION_WONT_FIX);
+ assertThat(issue.status()).isEqualTo(STATUS_RESOLVED);
+
+ // should remove assignee
+ assertThat(issue.assignee()).isNull();
+ }
+
+ /**
+ * User marks the manual issue as resolved -> issue is automatically
+ * closed.
+ */
+ @Test
+ public void automatically_close_resolved_manual_issue() {
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("ABCDE")
+ .setStatus(STATUS_OPEN)
+ .setRuleKey(RuleKey.of(MANUAL_REPOSITORY_KEY, "Performance"));
+
+ workflow.start();
+
+ assertThat(workflow.outTransitions(issue)).containsOnly(
+ Transition.create("confirm", "OPEN", "CONFIRMED"),
+ Transition.create("resolve", "OPEN", "RESOLVED"),
+ Transition.create("falsepositive", "OPEN", "RESOLVED"),
+ Transition.create("wontfix", "OPEN", "RESOLVED"));
+
+ workflow.doTransition(issue, "resolve", mock(IssueChangeContext.class));
+ assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
+ assertThat(issue.status()).isEqualTo("RESOLVED");
+
+ assertThat(workflow.outTransitions(issue)).containsOnly(
+ Transition.create("reopen", "RESOLVED", "REOPENED"));
+
+ workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
+ assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
+ assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
+ }
+
+ /**
+ * Manual issue is fixed because the file does not exist anymore
+ * or the tracking engine did not find the associated code
+ * -> the issue is closed
+ */
+ @Test
+ public void automatically_close_manual_issue_on_deleted_code() {
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("ABCDE")
+ .setStatus(STATUS_OPEN)
+ .setRuleKey(RuleKey.of(MANUAL_REPOSITORY_KEY, "Performance"))
+ .setBeingClosed(true);
+
+ workflow.start();
+
+ workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
+ assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
+ assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
+ }
+
+ /**
+ * Corner-case : the manual issue was marked as resolved by user but at the same
+ * time the file or the associated line was deleted.
+ */
+ @Test
+ public void automatically_close_resolved_manual_issue_on_deleted_code() {
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("ABCDE")
+ .setRuleKey(RuleKey.of(MANUAL_REPOSITORY_KEY, "Performance"))
+
+ // resolved by user
+ .setResolution(RESOLUTION_FIXED)
+ .setStatus(STATUS_RESOLVED)
+
+ // but unmatched by tracking engine
+ .setBeingClosed(true);
+
+ workflow.start();
+
+ workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
+ assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
+ assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
+ }
+
+ @Test
+ public void manual_issues_be_confirmed_then_kept_open() {
+ // Manual issue because of reporter
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("ABCDE")
+ .setStatus(STATUS_OPEN)
+ .setRuleKey(RuleKey.of("manual", "Performance"))
+ .setReporter("simon");
+
+ workflow.start();
+
+ assertThat(workflow.outTransitions(issue)).containsOnly(
+ Transition.create("confirm", "OPEN", "CONFIRMED"),
+ Transition.create("resolve", "OPEN", "RESOLVED"),
+ Transition.create("falsepositive", "OPEN", "RESOLVED"),
+ Transition.create("wontfix", "OPEN", "RESOLVED"));
+
+ workflow.doTransition(issue, "confirm", mock(IssueChangeContext.class));
+ assertThat(issue.resolution()).isNull();
+ assertThat(issue.status()).isEqualTo("CONFIRMED");
+
+ assertThat(workflow.outTransitions(issue)).containsOnly(
+ Transition.create("unconfirm", "CONFIRMED", "REOPENED"),
+ Transition.create("resolve", "CONFIRMED", "RESOLVED"),
+ Transition.create("falsepositive", "CONFIRMED", "RESOLVED"),
+ Transition.create("wontfix", "CONFIRMED", "RESOLVED"));
+
+ // keep confirmed and unresolved
+ workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
+ assertThat(issue.resolution()).isNull();
+ assertThat(issue.status()).isEqualTo("CONFIRMED");
+
+ // unconfirm
+ workflow.doTransition(issue, "unconfirm", mock(IssueChangeContext.class));
+ assertThat(issue.resolution()).isNull();
+ assertThat(issue.status()).isEqualTo("REOPENED");
+ }
+
+ @Test
+ public void manual_issue_on_removed_rule_be_closed() {
+ // Manual issue because of reporter
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("ABCDE")
+ .setStatus(STATUS_OPEN)
+ .setRuleKey(RuleKey.of("manual", "Performance"))
+ .setReporter("simon")
+ .setBeingClosed(true)
+ .setOnDisabledRule(true);
+
+ workflow.start();
+
+ workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
+ assertThat(issue.resolution()).isEqualTo("REMOVED");
+ assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
+ }
+
+ @Test
+ public void manual_issue_on_removed_component_be_closed() {
+ // Manual issue because of reporter
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("ABCDE")
+ .setStatus(STATUS_OPEN)
+ .setRuleKey(RuleKey.of("manual", "Performance"))
+ .setReporter("simon")
+ .setBeingClosed(true)
+ .setOnDisabledRule(false);
+
+ workflow.start();
+
+ workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
+ assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
+ assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
+ }
+
+ private Collection<String> keys(List<Transition> transitions) {
+ return Collections2.transform(transitions, new Function<Transition, String>() {
+ @Override
+ public String apply(@Nullable Transition transition) {
+ return transition.key();
+ }
+ });
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import org.junit.Test;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.condition.Condition;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class OrConditionTest {
+
+ private static final Condition TRUE_CONDITION = new BooleanCondition(true);
+ private static final Condition FALSE_CONDITION = new BooleanCondition(false);
+ Issue issue = mock(Issue.class);
+
+ @Test
+ public void match() {
+ assertThat(new OrCondition(TRUE_CONDITION).matches(issue)).isTrue();
+ assertThat(new OrCondition(FALSE_CONDITION).matches(issue)).isFalse();
+ assertThat(new OrCondition(FALSE_CONDITION, TRUE_CONDITION).matches(issue)).isTrue();
+ assertThat(new OrCondition(FALSE_CONDITION, FALSE_CONDITION).matches(issue)).isFalse();
+ }
+
+ private static class BooleanCondition implements Condition {
+ private final boolean b;
+
+ public BooleanCondition(boolean b) {
+ this.b = b;
+ }
+
+ @Override
+ public boolean matches(Issue issue) {
+ return b;
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import org.junit.Test;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+public class SetCloseDateTest {
+ @Test
+ public void should_set_close_date() {
+ SetCloseDate function = new SetCloseDate(true);
+ Function.Context context = mock(Function.Context.class);
+ function.execute(context);
+ verify(context, times(1)).setCloseDate(true);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import org.junit.Test;
+import org.sonar.api.issue.Issue;
+import org.sonar.core.issue.DefaultIssue;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.issue.workflow.SetClosed.INSTANCE;
+
+public class SetClosedTest {
+
+ Function.Context context = mock(Function.Context.class);
+
+ @Test
+ public void should_resolve_as_fixed() {
+ Issue issue = new DefaultIssue().setBeingClosed(true).setOnDisabledRule(false);
+ when(context.issue()).thenReturn(issue);
+ INSTANCE.execute(context);
+ verify(context, times(1)).setResolution(Issue.RESOLUTION_FIXED);
+ }
+
+ @Test
+ public void should_resolve_as_removed_when_rule_is_disabled() {
+ Issue issue = new DefaultIssue().setBeingClosed(true).setOnDisabledRule(true);
+ when(context.issue()).thenReturn(issue);
+ INSTANCE.execute(context);
+ verify(context, times(1)).setResolution(Issue.RESOLUTION_REMOVED);
+ }
+
+ @Test
+ public void line_number_must_be_unset() {
+ Issue issue = new DefaultIssue().setBeingClosed(true).setLine(10);
+ when(context.issue()).thenReturn(issue);
+ INSTANCE.execute(context);
+ verify(context).setLine(null);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import org.junit.Test;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+public class SetResolutionTest {
+ @Test
+ public void execute() {
+ SetResolution function = new SetResolution("FIXED");
+ Function.Context context = mock(Function.Context.class);
+ function.execute(context);
+ verify(context, times(1)).setResolution("FIXED");
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class StateMachineTest {
+ @Test
+ public void keep_order_of_state_keys() {
+ StateMachine machine = StateMachine.builder().states("OPEN", "RESOLVED", "CLOSED").build();
+
+ assertThat(machine.stateKeys()).containsSequence("OPEN", "RESOLVED", "CLOSED");
+ }
+
+ @Test
+ public void stateKey() {
+ StateMachine machine = StateMachine.builder()
+ .states("OPEN", "RESOLVED", "CLOSED")
+ .transition(Transition.builder("resolve").from("OPEN").to("RESOLVED").build())
+ .build();
+
+ assertThat(machine.state("OPEN")).isNotNull();
+ assertThat(machine.state("OPEN").transition("resolve")).isNotNull();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+public class StateTest {
+
+ Transition t1 = Transition.builder("close").from("OPEN").to("CLOSED").build();
+
+ @Test
+ public void key_should_be_set() {
+ try {
+ new State("", new Transition[0]);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessage("State key must be set");
+ }
+ }
+
+ @Test
+ public void key_should_be_upper_case() {
+ try {
+ new State("close", new Transition[0]);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessage("State key must be upper-case");
+ }
+ }
+
+ @Test
+ public void no_duplicated_out_transitions() {
+ try {
+ new State("CLOSE", new Transition[] {t1, t1});
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessage("Transition 'close' is declared several times from the originating state 'CLOSE'");
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import org.junit.Test;
+import org.sonar.api.issue.condition.Condition;
+import org.sonar.core.issue.DefaultIssue;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TransitionTest {
+
+ Condition condition1 = mock(Condition.class);
+ Condition condition2 = mock(Condition.class);
+ Function function1 = mock(Function.class);
+ Function function2 = mock(Function.class);
+
+ @Test
+ public void test_builder() throws Exception {
+ Transition transition = Transition.builder("close")
+ .from("OPEN").to("CLOSED")
+ .conditions(condition1, condition2)
+ .functions(function1, function2)
+ .build();
+ assertThat(transition.key()).isEqualTo("close");
+ assertThat(transition.from()).isEqualTo("OPEN");
+ assertThat(transition.to()).isEqualTo("CLOSED");
+ assertThat(transition.conditions()).containsOnly(condition1, condition2);
+ assertThat(transition.functions()).containsOnly(function1, function2);
+ assertThat(transition.automatic()).isFalse();
+ }
+
+ @Test
+ public void test_simplest_transition() throws Exception {
+ Transition transition = Transition.builder("close")
+ .from("OPEN").to("CLOSED")
+ .build();
+ assertThat(transition.key()).isEqualTo("close");
+ assertThat(transition.from()).isEqualTo("OPEN");
+ assertThat(transition.to()).isEqualTo("CLOSED");
+ assertThat(transition.conditions()).isEmpty();
+ assertThat(transition.functions()).isEmpty();
+ }
+
+ @Test
+ public void key_should_be_set() {
+ try {
+ Transition.builder("").from("OPEN").to("CLOSED").build();
+ fail();
+ } catch (Exception e) {
+ assertThat(e).hasMessage("Transition key must be set");
+ }
+ }
+
+ @Test
+ public void key_should_be_lower_case() {
+ try {
+ Transition.builder("CLOSE").from("OPEN").to("CLOSED").build();
+ fail();
+ } catch (Exception e) {
+ assertThat(e).hasMessage("Transition key must be lower-case");
+ }
+ }
+
+ @Test
+ public void originating_status_should_be_set() {
+ try {
+ Transition.builder("close").from("").to("CLOSED").build();
+ fail();
+ } catch (Exception e) {
+ assertThat(e).hasMessage("Originating status must be set");
+ }
+ }
+
+ @Test
+ public void destination_status_should_be_set() {
+ try {
+ Transition.builder("close").from("OPEN").to("").build();
+ fail();
+ } catch (Exception e) {
+ assertThat(e).hasMessage("Destination status must be set");
+ }
+ }
+
+ @Test
+ public void should_verify_conditions() {
+ DefaultIssue issue = new DefaultIssue();
+ Transition transition = Transition.builder("close")
+ .from("OPEN").to("CLOSED")
+ .conditions(condition1, condition2)
+ .build();
+
+ when(condition1.matches(issue)).thenReturn(true);
+ when(condition2.matches(issue)).thenReturn(false);
+ assertThat(transition.supports(issue)).isFalse();
+
+ when(condition1.matches(issue)).thenReturn(true);
+ when(condition2.matches(issue)).thenReturn(true);
+ assertThat(transition.supports(issue)).isTrue();
+ }
+
+ @Test
+ public void test_equals_and_hashCode() throws Exception {
+ Transition t1 = Transition.create("resolve", "OPEN", "RESOLVED");
+ Transition t2 = Transition.create("resolve", "REOPENED", "RESOLVED");
+ Transition t3 = Transition.create("confirm", "OPEN", "CONFIRMED");
+
+ assertThat(t1).isNotEqualTo(t2);
+ assertThat(t1).isNotEqualTo(t3);
+ assertThat(t1).isEqualTo(t1);
+
+ assertThat(t1.hashCode()).isEqualTo(t1.hashCode());
+ }
+
+ @Test
+ public void test_toString() throws Exception {
+ Transition t1 = Transition.create("resolve", "OPEN", "RESOLVED");
+ assertThat(t1.toString()).isEqualTo("OPEN->resolve->RESOLVED");
+ }
+
+ @Test
+ public void test_automatic_transition() throws Exception {
+ Transition transition = Transition.builder("close")
+ .from("OPEN").to("CLOSED")
+ .automatic()
+ .build();
+ assertThat(transition.automatic()).isTrue();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.issue.workflow;
+
+import org.junit.Test;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.sonar.server.issue.workflow.UnsetAssignee.INSTANCE;
+
+public class UnsetAssigneeTest {
+
+ @Test
+ public void unassign() {
+ Function.Context context = mock(Function.Context.class);
+ INSTANCE.execute(context);
+ verify(context, times(1)).setAssignee(null);
+ }
+}
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.workflow.Transition;
+import org.sonar.server.issue.workflow.Transition;
import org.sonar.server.issue.ActionService;
import org.sonar.server.issue.IssueService;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.DefaultIssueComment;
import org.sonar.core.issue.FieldDiffs;
-import org.sonar.core.issue.workflow.Transition;
+import org.sonar.server.issue.workflow.Transition;
import org.sonar.core.user.DefaultUser;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.batch.source.CodeColorizers;
import org.sonar.batch.test.TestPlanBuilder;
import org.sonar.batch.test.TestableBuilder;
-import org.sonar.core.issue.workflow.FunctionExecutor;
import org.sonar.core.metric.BatchMetrics;
import org.sonar.core.platform.ComponentContainer;
new QualityProfileProvider(),
// issues
- FunctionExecutor.class,
IssueCache.class,
DefaultProjectIssues.class,
IssueTransition.class,
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import javax.annotation.Nullable;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.user.User;
-
-interface Function {
- interface Context {
- Issue issue();
-
- Context setAssignee(@Nullable User user);
-
- Context setResolution(@Nullable String s);
-
- Context setCloseDate(boolean b);
-
- Context setLine(@Nullable Integer line);
- }
-
- void execute(Context context);
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import javax.annotation.Nullable;
-import org.sonar.api.batch.BatchSide;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.user.User;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.IssueChangeContext;
-import org.sonar.core.issue.IssueUpdater;
-
-@BatchSide
-@ServerSide
-public class FunctionExecutor {
-
- private final IssueUpdater updater;
-
- public FunctionExecutor(IssueUpdater updater) {
- this.updater = updater;
- }
-
- public void execute(Function[] functions, DefaultIssue issue, IssueChangeContext changeContext) {
- if (functions.length > 0) {
- FunctionContext functionContext = new FunctionContext(updater, issue, changeContext);
- for (Function function : functions) {
- function.execute(functionContext);
- }
- }
- }
-
- static class FunctionContext implements Function.Context {
- private final IssueUpdater updater;
- private final DefaultIssue issue;
- private final IssueChangeContext changeContext;
-
- FunctionContext(IssueUpdater updater, DefaultIssue issue, IssueChangeContext changeContext) {
- this.updater = updater;
- this.issue = issue;
- this.changeContext = changeContext;
- }
-
- @Override
- public Issue issue() {
- return issue;
- }
-
- @Override
- public Function.Context setAssignee(@Nullable User user) {
- updater.assign(issue, user, changeContext);
- return this;
- }
-
- @Override
- public Function.Context setResolution(@Nullable String s) {
- updater.setResolution(issue, s, changeContext);
- return this;
- }
-
- @Override
- public Function.Context setCloseDate(boolean b) {
- updater.setCloseDate(issue, b ? changeContext.date() : null, changeContext);
- return this;
- }
-
- @Override
- public Function.Context setLine(@Nullable Integer line) {
- updater.setLine(issue, line);
- return this;
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.condition.Condition;
-import org.sonar.core.issue.DefaultIssue;
-
-enum IsBeingClosed implements Condition {
- INSTANCE;
-
- @Override
- public boolean matches(Issue issue) {
- return ((DefaultIssue) issue).isBeingClosed();
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.condition.Condition;
-
-enum IsManual implements Condition {
- INSTANCE;
-
- @Override
- public boolean matches(Issue issue) {
- return issue.ruleKey().isManual();
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import java.util.List;
-import org.picocontainer.Startable;
-import org.sonar.api.issue.DefaultTransitions;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.condition.HasResolution;
-import org.sonar.api.issue.condition.NotCondition;
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.web.UserRole;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.IssueChangeContext;
-import org.sonar.core.issue.IssueUpdater;
-
-@ServerSide
-public class IssueWorkflow implements Startable {
-
- private final FunctionExecutor functionExecutor;
- private final IssueUpdater updater;
- private StateMachine machine;
-
- public IssueWorkflow(FunctionExecutor functionExecutor, IssueUpdater updater) {
- this.functionExecutor = functionExecutor;
- this.updater = updater;
- }
-
- @Override
- public void start() {
- StateMachine.Builder builder = StateMachine.builder()
- // order is important for UI
- .states(Issue.STATUS_OPEN, Issue.STATUS_CONFIRMED, Issue.STATUS_REOPENED, Issue.STATUS_RESOLVED, Issue.STATUS_CLOSED);
-
- buildManualTransitions(builder);
- buildAutomaticTransitions(builder);
- machine = builder.build();
- }
-
- private void buildManualTransitions(StateMachine.Builder builder) {
- builder.transition(Transition.builder(DefaultTransitions.CONFIRM)
- .from(Issue.STATUS_OPEN).to(Issue.STATUS_CONFIRMED)
- .functions(new SetResolution(null))
- .build())
- .transition(Transition.builder(DefaultTransitions.CONFIRM)
- .from(Issue.STATUS_REOPENED).to(Issue.STATUS_CONFIRMED)
- .functions(new SetResolution(null))
- .build())
- .transition(Transition.builder(DefaultTransitions.UNCONFIRM)
- .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_REOPENED)
- .functions(new SetResolution(null))
- .build())
- .transition(Transition.builder(DefaultTransitions.RESOLVE)
- .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
- .functions(new SetResolution(Issue.RESOLUTION_FIXED))
- .build())
- .transition(Transition.builder(DefaultTransitions.RESOLVE)
- .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED)
- .functions(new SetResolution(Issue.RESOLUTION_FIXED))
- .build())
- .transition(Transition.builder(DefaultTransitions.RESOLVE)
- .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED)
- .functions(new SetResolution(Issue.RESOLUTION_FIXED))
- .build())
- .transition(Transition.builder(DefaultTransitions.REOPEN)
- .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED)
- .functions(new SetResolution(null))
- .build())
- .transition(Transition.builder(DefaultTransitions.REOPEN)
- .conditions(IsManual.INSTANCE)
- .from(Issue.STATUS_CLOSED).to(Issue.STATUS_REOPENED)
- .functions(new SetResolution(null), new SetCloseDate(false))
- .build())
-
- // resolve as false-positive
- .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
- .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
- .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
- .requiredProjectPermission(UserRole.ISSUE_ADMIN)
- .build())
- .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
- .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED)
- .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
- .requiredProjectPermission(UserRole.ISSUE_ADMIN)
- .build())
- .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
- .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED)
- .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
- .requiredProjectPermission(UserRole.ISSUE_ADMIN)
- .build())
-
- // resolve as won't fix
- .transition(Transition.builder(DefaultTransitions.WONT_FIX)
- .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
- .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
- .requiredProjectPermission(UserRole.ISSUE_ADMIN)
- .build())
- .transition(Transition.builder(DefaultTransitions.WONT_FIX)
- .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED)
- .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
- .requiredProjectPermission(UserRole.ISSUE_ADMIN)
- .build())
- .transition(Transition.builder(DefaultTransitions.WONT_FIX)
- .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED)
- .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
- .requiredProjectPermission(UserRole.ISSUE_ADMIN)
- .build());
-
- }
-
- private void buildAutomaticTransitions(StateMachine.Builder builder) {
- // Close the "end of life" issues (disabled/deleted rule, deleted component)
- builder
- .transition(Transition.builder("automaticclose")
- .from(Issue.STATUS_OPEN).to(Issue.STATUS_CLOSED)
- .conditions(IsBeingClosed.INSTANCE)
- .functions(SetClosed.INSTANCE, new SetCloseDate(true))
- .automatic()
- .build())
- .transition(Transition.builder("automaticclose")
- .from(Issue.STATUS_REOPENED).to(Issue.STATUS_CLOSED)
- .conditions(IsBeingClosed.INSTANCE)
- .functions(SetClosed.INSTANCE, new SetCloseDate(true))
- .automatic()
- .build())
- .transition(Transition.builder("automaticclose")
- .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_CLOSED)
- .conditions(IsBeingClosed.INSTANCE)
- .functions(SetClosed.INSTANCE, new SetCloseDate(true))
- .automatic()
- .build())
- .transition(Transition.builder("automaticclose")
- .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_CLOSED)
- .conditions(new OrCondition(IsBeingClosed.INSTANCE, IsManual.INSTANCE))
- .functions(SetClosed.INSTANCE, new SetCloseDate(true))
- .automatic()
- .build())
-
- // Reopen issues that are marked as resolved but that are still alive.
- // Manual issues are kept resolved.
- .transition(Transition.builder("automaticreopen")
- .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED)
- .conditions(new NotCondition(IsBeingClosed.INSTANCE), new HasResolution(Issue.RESOLUTION_FIXED), new NotCondition(IsManual.INSTANCE))
- .functions(new SetResolution(null), new SetCloseDate(false))
- .automatic()
- .build());
- }
-
- @Override
- public void stop() {
- // nothing to do
- }
-
- public boolean doTransition(DefaultIssue issue, String transitionKey, IssueChangeContext issueChangeContext) {
- Transition transition = stateOf(issue).transition(transitionKey);
- if (transition != null && !transition.automatic()) {
- functionExecutor.execute(transition.functions(), issue, issueChangeContext);
- updater.setStatus(issue, transition.to(), issueChangeContext);
- return true;
- }
- return false;
- }
-
- public List<Transition> outTransitions(Issue issue) {
- State state = machine.state(issue.status());
- if (state == null) {
- throw new IllegalArgumentException("Unknown status: " + issue.status());
- }
- return state.outManualTransitions(issue);
- }
-
- public void doAutomaticTransition(DefaultIssue issue, IssueChangeContext issueChangeContext) {
- Transition transition = stateOf(issue).outAutomaticTransition(issue);
- if (transition != null) {
- functionExecutor.execute(transition.functions(), issue, issueChangeContext);
- updater.setStatus(issue, transition.to(), issueChangeContext);
- }
- }
-
- public List<String> statusKeys() {
- return machine.stateKeys();
- }
-
- private State stateOf(DefaultIssue issue) {
- State state = machine.state(issue.status());
- if (state == null) {
- throw new IllegalStateException("Unknown status: " + issue.status() + " [issue=" + issue.key() + "]");
- }
- return state;
- }
-
- StateMachine machine() {
- return machine;
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.condition.Condition;
-
-public class OrCondition implements Condition {
- private final Condition[] conditions;
-
- public OrCondition(Condition... conditions) {
- this.conditions = conditions;
- }
-
- @Override
- public boolean matches(Issue issue) {
- for (Condition condition : conditions) {
- if (condition.matches(issue)) {
- return true;
- }
- }
- return false;
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-class SetCloseDate implements Function {
- private final boolean set;
-
- public SetCloseDate(boolean set) {
- this.set = set;
- }
-
- @Override
- public void execute(Context context) {
- context.setCloseDate(set);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import org.sonar.api.issue.Issue;
-import org.sonar.core.issue.DefaultIssue;
-
-public enum SetClosed implements Function {
- INSTANCE;
-
- @Override
- public void execute(Context context) {
- DefaultIssue issue = (DefaultIssue) context.issue();
- if (issue.isOnDisabledRule()) {
- context.setResolution(Issue.RESOLUTION_REMOVED);
- } else {
- context.setResolution(Issue.RESOLUTION_FIXED);
- }
-
- // closed issues are not "tracked" -> the line number does not evolve anymore
- // when code changes. That's misleading for end-users, so line number
- // is unset.
- context.setLine(null);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import javax.annotation.Nullable;
-
-public class SetResolution implements Function {
- private final String resolution;
-
- public SetResolution(@Nullable String resolution) {
- this.resolution = resolution;
- }
-
- @Override
- public void execute(Context context) {
- context.setResolution(resolution);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import java.util.List;
-import java.util.Set;
-import javax.annotation.CheckForNull;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.issue.Issue;
-
-public class State {
- private final String key;
- private final Transition[] outTransitions;
-
- public State(String key, Transition[] outTransitions) {
- Preconditions.checkArgument(!Strings.isNullOrEmpty(key), "State key must be set");
- Preconditions.checkArgument(StringUtils.isAllUpperCase(key), "State key must be upper-case");
- checkDuplications(outTransitions, key);
-
- this.key = key;
- this.outTransitions = outTransitions;
- }
-
- private static void checkDuplications(Transition[] transitions, String stateKey) {
- Set<String> keys = Sets.newHashSet();
- for (Transition transition : transitions) {
- if (keys.contains(transition.key())) {
- throw new IllegalArgumentException("Transition '" + transition.key() +
- "' is declared several times from the originating state '" + stateKey + "'");
- }
- keys.add(transition.key());
- }
- }
-
- public List<Transition> outManualTransitions(Issue issue) {
- List<Transition> result = Lists.newArrayList();
- for (Transition transition : outTransitions) {
- if (!transition.automatic() && transition.supports(issue)) {
- result.add(transition);
- }
- }
- return result;
- }
-
- @CheckForNull
- public Transition outAutomaticTransition(Issue issue) {
- Transition result = null;
- for (Transition transition : outTransitions) {
- if (transition.automatic() && transition.supports(issue)) {
- if (result == null) {
- result = transition;
- } else {
- throw new IllegalStateException("Several automatic transitions are available for issue: " + issue);
- }
- }
- }
- return result;
- }
-
- Transition transition(String transitionKey) {
- for (Transition transition : outTransitions) {
- if (transitionKey.equals(transition.key())) {
- return transition;
- }
- }
- throw new IllegalStateException("Transition from state " + key + " does not exist: " + transitionKey);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Sets;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import javax.annotation.CheckForNull;
-
-public class StateMachine {
-
- private final List<String> keys;
- private final Map<String, State> byKey;
-
- private StateMachine(Builder builder) {
- this.keys = ImmutableList.copyOf(builder.states);
- ImmutableMap.Builder<String, State> mapBuilder = ImmutableMap.builder();
- for (String stateKey : builder.states) {
- List<Transition> outTransitions = builder.outTransitions.get(stateKey);
- State state = new State(stateKey, outTransitions.toArray(new Transition[outTransitions.size()]));
- mapBuilder.put(stateKey, state);
- }
- byKey = mapBuilder.build();
- }
-
- @CheckForNull
- public State state(String stateKey) {
- return byKey.get(stateKey);
- }
-
- public List<String> stateKeys() {
- return keys;
- }
-
- public static Builder builder() {
- return new Builder();
- }
-
- public static class Builder {
- private final Set<String> states = Sets.newLinkedHashSet();
- // transitions per originating state
- private final ListMultimap<String, Transition> outTransitions = ArrayListMultimap.create();
-
- private Builder() {
- }
-
- public Builder states(String... keys) {
- states.addAll(Arrays.asList(keys));
- return this;
- }
-
- public Builder transition(Transition transition) {
- Preconditions.checkArgument(states.contains(transition.from()), "Originating state does not exist: " + transition.from());
- Preconditions.checkArgument(states.contains(transition.to()), "Destination state does not exist: " + transition.to());
- outTransitions.put(transition.from(), transition);
- return this;
- }
-
- public StateMachine build() {
- Preconditions.checkArgument(!states.isEmpty(), "At least one state is required");
- return new StateMachine(this);
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
-import java.util.Arrays;
-import java.util.List;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.condition.Condition;
-
-public class Transition {
- private final String key;
- private final String from;
- private final String to;
- private final Condition[] conditions;
- private final Function[] functions;
- private final boolean automatic;
- private String requiredProjectPermission;
-
- private Transition(TransitionBuilder builder) {
- key = builder.key;
- from = builder.from;
- to = builder.to;
- conditions = builder.conditions.toArray(new Condition[builder.conditions.size()]);
- functions = builder.functions.toArray(new Function[builder.functions.size()]);
- automatic = builder.automatic;
- requiredProjectPermission = builder.requiredProjectPermission;
- }
-
- public String key() {
- return key;
- }
-
- String from() {
- return from;
- }
-
- String to() {
- return to;
- }
-
- Condition[] conditions() {
- return conditions;
- }
-
- Function[] functions() {
- return functions;
- }
-
- boolean automatic() {
- return automatic;
- }
-
- public boolean supports(Issue issue) {
- for (Condition condition : conditions) {
- if (!condition.matches(issue)) {
- return false;
- }
- }
- return true;
- }
-
- public String requiredProjectPermission() {
- return requiredProjectPermission;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- Transition that = (Transition) o;
- if (!from.equals(that.from)) {
- return false;
- }
- if (!key.equals(that.key)) {
- return false;
- }
- return to.equals(that.to);
- }
-
- @Override
- public int hashCode() {
- int result = key.hashCode();
- result = 31 * result + from.hashCode();
- result = 31 * result + to.hashCode();
- return result;
- }
-
- @Override
- public String toString() {
- return String.format("%s->%s->%s", from, key, to);
- }
-
- public static Transition create(String key, String from, String to) {
- return builder(key).from(from).to(to).build();
- }
-
- public static TransitionBuilder builder(String key) {
- return new TransitionBuilder(key);
- }
-
- public static class TransitionBuilder {
- private final String key;
- private String from;
- private String to;
- private List<Condition> conditions = Lists.newArrayList();
- private List<Function> functions = Lists.newArrayList();
- private boolean automatic = false;
- private String requiredProjectPermission;
-
- private TransitionBuilder(String key) {
- this.key = key;
- }
-
- public TransitionBuilder from(String from) {
- this.from = from;
- return this;
- }
-
- public TransitionBuilder to(String to) {
- this.to = to;
- return this;
- }
-
- public TransitionBuilder conditions(Condition... c) {
- this.conditions.addAll(Arrays.asList(c));
- return this;
- }
-
- public TransitionBuilder functions(Function... f) {
- this.functions.addAll(Arrays.asList(f));
- return this;
- }
-
- public TransitionBuilder automatic() {
- this.automatic = true;
- return this;
- }
-
- public TransitionBuilder requiredProjectPermission(String requiredProjectPermission) {
- this.requiredProjectPermission = requiredProjectPermission;
- return this;
- }
-
- public Transition build() {
- Preconditions.checkArgument(!Strings.isNullOrEmpty(key), "Transition key must be set");
- Preconditions.checkArgument(StringUtils.isAllLowerCase(key), "Transition key must be lower-case");
- Preconditions.checkArgument(!Strings.isNullOrEmpty(from), "Originating status must be set");
- Preconditions.checkArgument(!Strings.isNullOrEmpty(to), "Destination status must be set");
- return new Transition(this);
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-enum UnsetAssignee implements Function {
- INSTANCE;
-
- @Override
- public void execute(Context context) {
- context.setAssignee(null);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.core.issue.workflow;
-
-import javax.annotation.ParametersAreNonnullByDefault;
-
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import org.junit.Test;
-import org.sonar.core.issue.DefaultIssue;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.core.issue.workflow.IsBeingClosed.INSTANCE;
-
-public class IsBeingClosedTest {
-
- @Test
- public void should_be_end_of_life() {
- DefaultIssue issue = new DefaultIssue();
- assertThat(INSTANCE.matches(issue.setBeingClosed(true))).isTrue();
- assertThat(INSTANCE.matches(issue.setBeingClosed(false))).isFalse();
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import org.junit.Test;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.core.issue.DefaultIssue;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.core.issue.workflow.IsManual.INSTANCE;
-
-public class IsManualTest {
-
- @Test
- public void should_match() {
- DefaultIssue issue = new DefaultIssue();
- assertThat(INSTANCE.matches(issue.setRuleKey(RuleKey.of(RuleKey.MANUAL_REPOSITORY_KEY, "R1")))).isTrue();
- assertThat(INSTANCE.matches(issue.setRuleKey(RuleKey.of("java", "R1")))).isFalse();
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import com.google.common.base.Function;
-import com.google.common.collect.Collections2;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
-import javax.annotation.Nullable;
-import org.apache.commons.lang.time.DateUtils;
-import org.junit.Test;
-import org.sonar.api.issue.DefaultTransitions;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.IssueChangeContext;
-import org.sonar.core.issue.IssueUpdater;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
-import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
-import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX;
-import static org.sonar.api.issue.Issue.STATUS_CLOSED;
-import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
-import static org.sonar.api.issue.Issue.STATUS_OPEN;
-import static org.sonar.api.issue.Issue.STATUS_REOPENED;
-import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
-import static org.sonar.api.rule.RuleKey.MANUAL_REPOSITORY_KEY;
-
-public class IssueWorkflowTest {
-
- IssueUpdater updater = new IssueUpdater();
- IssueWorkflow workflow = new IssueWorkflow(new FunctionExecutor(updater), updater);
-
- @Test
- public void init_state_machine() {
- assertThat(workflow.machine()).isNull();
- workflow.start();
- assertThat(workflow.machine()).isNotNull();
- assertThat(workflow.machine().state(STATUS_OPEN)).isNotNull();
- assertThat(workflow.machine().state(STATUS_CONFIRMED)).isNotNull();
- assertThat(workflow.machine().state(STATUS_CLOSED)).isNotNull();
- assertThat(workflow.machine().state(STATUS_REOPENED)).isNotNull();
- assertThat(workflow.machine().state(STATUS_RESOLVED)).isNotNull();
- workflow.stop();
- }
-
- @Test
- public void list_statuses() {
- workflow.start();
- // order is important for UI
- assertThat(workflow.statusKeys()).containsSequence(STATUS_OPEN, STATUS_CONFIRMED, STATUS_REOPENED, STATUS_RESOLVED, STATUS_CLOSED);
- }
-
- @Test
- public void list_out_transitions_from_status_open() {
- workflow.start();
-
- DefaultIssue issue = new DefaultIssue().setStatus(STATUS_OPEN);
- List<Transition> transitions = workflow.outTransitions(issue);
- assertThat(keys(transitions)).containsOnly("confirm", "falsepositive", "resolve", "wontfix");
- }
-
- @Test
- public void list_out_transitions_from_status_confirmed() {
- workflow.start();
-
- DefaultIssue issue = new DefaultIssue().setStatus(STATUS_CONFIRMED);
- List<Transition> transitions = workflow.outTransitions(issue);
- assertThat(keys(transitions)).containsOnly("unconfirm", "falsepositive", "resolve", "wontfix");
- }
-
- @Test
- public void list_out_transitions_from_status_resolved() {
- workflow.start();
-
- DefaultIssue issue = new DefaultIssue().setStatus(STATUS_RESOLVED);
- List<Transition> transitions = workflow.outTransitions(issue);
- assertThat(keys(transitions)).containsOnly("reopen");
- }
-
- @Test
- public void list_out_transitions_from_status_reopen() {
- workflow.start();
-
- DefaultIssue issue = new DefaultIssue().setStatus(STATUS_REOPENED);
- List<Transition> transitions = workflow.outTransitions(issue);
- assertThat(keys(transitions)).containsOnly("confirm", "resolve", "falsepositive", "wontfix");
- }
-
- @Test
- public void list_no_out_transition_from_status_closed() {
- workflow.start();
-
- DefaultIssue issue = new DefaultIssue().setStatus(STATUS_CLOSED).setRuleKey(RuleKey.of("java", "R1 "));
- List<Transition> transitions = workflow.outTransitions(issue);
- assertThat(transitions).isEmpty();
- }
-
- @Test
- public void list_out_transitions_from_status_closed_on_manual_issue() {
- workflow.start();
-
- // Manual issue because of reporter
- DefaultIssue issue = new DefaultIssue()
- .setKey("ABCDE")
- .setStatus(STATUS_CLOSED)
- .setRuleKey(RuleKey.of("manual", "Performance"))
- .setReporter("simon");
-
- List<Transition> transitions = workflow.outTransitions(issue);
- assertThat(keys(transitions)).containsOnly("reopen");
- }
-
- @Test
- public void fail_if_unknown_status_when_listing_transitions() {
- workflow.start();
-
- DefaultIssue issue = new DefaultIssue().setStatus("xxx");
- try {
- workflow.outTransitions(issue);
- fail();
- } catch (IllegalArgumentException e) {
- assertThat(e).hasMessage("Unknown status: xxx");
- }
- }
-
- @Test
- public void automatically_close_resolved_issue() {
- workflow.start();
-
- DefaultIssue issue = new DefaultIssue()
- .setKey("ABCDE")
- .setRuleKey(RuleKey.of("js", "S001"))
- .setResolution(RESOLUTION_FIXED)
- .setStatus(STATUS_RESOLVED)
- .setNew(false)
- .setBeingClosed(true);
- Date now = new Date();
- workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
- assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
- assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
- assertThat(issue.closeDate()).isNotNull();
- assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
- }
-
- @Test
- public void close_open_dead_issue() {
- workflow.start();
-
- DefaultIssue issue = new DefaultIssue()
- .setKey("ABCDE")
- .setResolution(null)
- .setStatus(STATUS_OPEN)
- .setNew(false)
- .setBeingClosed(true);
- Date now = new Date();
- workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
- assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
- assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
- assertThat(issue.closeDate()).isNotNull();
- assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
- }
-
- @Test
- public void close_reopened_dead_issue() {
- workflow.start();
-
- DefaultIssue issue = new DefaultIssue()
- .setKey("ABCDE")
- .setResolution(null)
- .setStatus(STATUS_REOPENED)
- .setNew(false)
- .setBeingClosed(true);
- Date now = new Date();
- workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
- assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
- assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
- assertThat(issue.closeDate()).isNotNull();
- assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
- }
-
- @Test
- public void close_confirmed_dead_issue() {
- workflow.start();
-
- DefaultIssue issue = new DefaultIssue()
- .setKey("ABCDE")
- .setResolution(null)
- .setStatus(STATUS_CONFIRMED)
- .setNew(false)
- .setBeingClosed(true);
- Date now = new Date();
- workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
- assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
- assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
- assertThat(issue.closeDate()).isNotNull();
- assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
- }
-
- @Test
- public void fail_if_unknown_status_on_automatic_trans() {
- workflow.start();
-
- DefaultIssue issue = new DefaultIssue()
- .setKey("ABCDE")
- .setResolution(RESOLUTION_FIXED)
- .setStatus("xxx")
- .setNew(false)
- .setBeingClosed(true);
- try {
- workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(new Date()));
- fail();
- } catch (IllegalStateException e) {
- assertThat(e).hasMessage("Unknown status: xxx [issue=ABCDE]");
- }
- }
-
- @Test
- public void flag_as_false_positive() {
- DefaultIssue issue = new DefaultIssue()
- .setKey("ABCDE")
- .setStatus(STATUS_OPEN)
- .setRuleKey(RuleKey.of("squid", "AvoidCycle"))
- .setAssignee("morgan");
-
- workflow.start();
- workflow.doTransition(issue, DefaultTransitions.FALSE_POSITIVE, IssueChangeContext.createScan(new Date()));
-
- assertThat(issue.resolution()).isEqualTo(RESOLUTION_FALSE_POSITIVE);
- assertThat(issue.status()).isEqualTo(STATUS_RESOLVED);
-
- // should remove assignee
- assertThat(issue.assignee()).isNull();
- }
-
- @Test
- public void wont_fix() {
- DefaultIssue issue = new DefaultIssue()
- .setKey("ABCDE")
- .setStatus(STATUS_OPEN)
- .setRuleKey(RuleKey.of("squid", "AvoidCycle"))
- .setAssignee("morgan");
-
- workflow.start();
- workflow.doTransition(issue, DefaultTransitions.WONT_FIX, IssueChangeContext.createScan(new Date()));
-
- assertThat(issue.resolution()).isEqualTo(RESOLUTION_WONT_FIX);
- assertThat(issue.status()).isEqualTo(STATUS_RESOLVED);
-
- // should remove assignee
- assertThat(issue.assignee()).isNull();
- }
-
- /**
- * User marks the manual issue as resolved -> issue is automatically
- * closed.
- */
- @Test
- public void automatically_close_resolved_manual_issue() {
- DefaultIssue issue = new DefaultIssue()
- .setKey("ABCDE")
- .setStatus(STATUS_OPEN)
- .setRuleKey(RuleKey.of(MANUAL_REPOSITORY_KEY, "Performance"));
-
- workflow.start();
-
- assertThat(workflow.outTransitions(issue)).containsOnly(
- Transition.create("confirm", "OPEN", "CONFIRMED"),
- Transition.create("resolve", "OPEN", "RESOLVED"),
- Transition.create("falsepositive", "OPEN", "RESOLVED"),
- Transition.create("wontfix", "OPEN", "RESOLVED"));
-
- workflow.doTransition(issue, "resolve", mock(IssueChangeContext.class));
- assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
- assertThat(issue.status()).isEqualTo("RESOLVED");
-
- assertThat(workflow.outTransitions(issue)).containsOnly(
- Transition.create("reopen", "RESOLVED", "REOPENED"));
-
- workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
- assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
- assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
- }
-
- /**
- * Manual issue is fixed because the file does not exist anymore
- * or the tracking engine did not find the associated code
- * -> the issue is closed
- */
- @Test
- public void automatically_close_manual_issue_on_deleted_code() {
- DefaultIssue issue = new DefaultIssue()
- .setKey("ABCDE")
- .setStatus(STATUS_OPEN)
- .setRuleKey(RuleKey.of(MANUAL_REPOSITORY_KEY, "Performance"))
- .setBeingClosed(true);
-
- workflow.start();
-
- workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
- assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
- assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
- }
-
- /**
- * Corner-case : the manual issue was marked as resolved by user but at the same
- * time the file or the associated line was deleted.
- */
- @Test
- public void automatically_close_resolved_manual_issue_on_deleted_code() {
- DefaultIssue issue = new DefaultIssue()
- .setKey("ABCDE")
- .setRuleKey(RuleKey.of(MANUAL_REPOSITORY_KEY, "Performance"))
-
- // resolved by user
- .setResolution(RESOLUTION_FIXED)
- .setStatus(STATUS_RESOLVED)
-
- // but unmatched by tracking engine
- .setBeingClosed(true);
-
- workflow.start();
-
- workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
- assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
- assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
- }
-
- @Test
- public void manual_issues_be_confirmed_then_kept_open() {
- // Manual issue because of reporter
- DefaultIssue issue = new DefaultIssue()
- .setKey("ABCDE")
- .setStatus(STATUS_OPEN)
- .setRuleKey(RuleKey.of("manual", "Performance"))
- .setReporter("simon");
-
- workflow.start();
-
- assertThat(workflow.outTransitions(issue)).containsOnly(
- Transition.create("confirm", "OPEN", "CONFIRMED"),
- Transition.create("resolve", "OPEN", "RESOLVED"),
- Transition.create("falsepositive", "OPEN", "RESOLVED"),
- Transition.create("wontfix", "OPEN", "RESOLVED"));
-
- workflow.doTransition(issue, "confirm", mock(IssueChangeContext.class));
- assertThat(issue.resolution()).isNull();
- assertThat(issue.status()).isEqualTo("CONFIRMED");
-
- assertThat(workflow.outTransitions(issue)).containsOnly(
- Transition.create("unconfirm", "CONFIRMED", "REOPENED"),
- Transition.create("resolve", "CONFIRMED", "RESOLVED"),
- Transition.create("falsepositive", "CONFIRMED", "RESOLVED"),
- Transition.create("wontfix", "CONFIRMED", "RESOLVED"));
-
- // keep confirmed and unresolved
- workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
- assertThat(issue.resolution()).isNull();
- assertThat(issue.status()).isEqualTo("CONFIRMED");
-
- // unconfirm
- workflow.doTransition(issue, "unconfirm", mock(IssueChangeContext.class));
- assertThat(issue.resolution()).isNull();
- assertThat(issue.status()).isEqualTo("REOPENED");
- }
-
- @Test
- public void manual_issue_on_removed_rule_be_closed() {
- // Manual issue because of reporter
- DefaultIssue issue = new DefaultIssue()
- .setKey("ABCDE")
- .setStatus(STATUS_OPEN)
- .setRuleKey(RuleKey.of("manual", "Performance"))
- .setReporter("simon")
- .setBeingClosed(true)
- .setOnDisabledRule(true);
-
- workflow.start();
-
- workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
- assertThat(issue.resolution()).isEqualTo("REMOVED");
- assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
- }
-
- @Test
- public void manual_issue_on_removed_component_be_closed() {
- // Manual issue because of reporter
- DefaultIssue issue = new DefaultIssue()
- .setKey("ABCDE")
- .setStatus(STATUS_OPEN)
- .setRuleKey(RuleKey.of("manual", "Performance"))
- .setReporter("simon")
- .setBeingClosed(true)
- .setOnDisabledRule(false);
-
- workflow.start();
-
- workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
- assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
- assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
- }
-
- private Collection<String> keys(List<Transition> transitions) {
- return Collections2.transform(transitions, new Function<Transition, String>() {
- @Override
- public String apply(@Nullable Transition transition) {
- return transition.key();
- }
- });
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import org.junit.Test;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.condition.Condition;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-
-public class OrConditionTest {
-
- private static final Condition TRUE_CONDITION = new BooleanCondition(true);
- private static final Condition FALSE_CONDITION = new BooleanCondition(false);
- Issue issue = mock(Issue.class);
-
- @Test
- public void match() {
- assertThat(new OrCondition(TRUE_CONDITION).matches(issue)).isTrue();
- assertThat(new OrCondition(FALSE_CONDITION).matches(issue)).isFalse();
- assertThat(new OrCondition(FALSE_CONDITION, TRUE_CONDITION).matches(issue)).isTrue();
- assertThat(new OrCondition(FALSE_CONDITION, FALSE_CONDITION).matches(issue)).isFalse();
- }
-
- private static class BooleanCondition implements Condition {
- private final boolean b;
-
- public BooleanCondition(boolean b) {
- this.b = b;
- }
-
- @Override
- public boolean matches(Issue issue) {
- return b;
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import org.junit.Test;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-public class SetCloseDateTest {
- @Test
- public void should_set_close_date() {
- SetCloseDate function = new SetCloseDate(true);
- Function.Context context = mock(Function.Context.class);
- function.execute(context);
- verify(context, times(1)).setCloseDate(true);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import org.junit.Test;
-import org.sonar.api.issue.Issue;
-import org.sonar.core.issue.DefaultIssue;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.sonar.core.issue.workflow.SetClosed.INSTANCE;
-
-public class SetClosedTest {
-
- Function.Context context = mock(Function.Context.class);
-
- @Test
- public void should_resolve_as_fixed() {
- Issue issue = new DefaultIssue().setBeingClosed(true).setOnDisabledRule(false);
- when(context.issue()).thenReturn(issue);
- INSTANCE.execute(context);
- verify(context, times(1)).setResolution(Issue.RESOLUTION_FIXED);
- }
-
- @Test
- public void should_resolve_as_removed_when_rule_is_disabled() {
- Issue issue = new DefaultIssue().setBeingClosed(true).setOnDisabledRule(true);
- when(context.issue()).thenReturn(issue);
- INSTANCE.execute(context);
- verify(context, times(1)).setResolution(Issue.RESOLUTION_REMOVED);
- }
-
- @Test
- public void line_number_must_be_unset() {
- Issue issue = new DefaultIssue().setBeingClosed(true).setLine(10);
- when(context.issue()).thenReturn(issue);
- INSTANCE.execute(context);
- verify(context).setLine(null);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import org.junit.Test;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-public class SetResolutionTest {
- @Test
- public void execute() {
- SetResolution function = new SetResolution("FIXED");
- Function.Context context = mock(Function.Context.class);
- function.execute(context);
- verify(context, times(1)).setResolution("FIXED");
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class StateMachineTest {
- @Test
- public void keep_order_of_state_keys() {
- StateMachine machine = StateMachine.builder().states("OPEN", "RESOLVED", "CLOSED").build();
-
- assertThat(machine.stateKeys()).containsSequence("OPEN", "RESOLVED", "CLOSED");
- }
-
- @Test
- public void stateKey() {
- StateMachine machine = StateMachine.builder()
- .states("OPEN", "RESOLVED", "CLOSED")
- .transition(Transition.builder("resolve").from("OPEN").to("RESOLVED").build())
- .build();
-
- assertThat(machine.state("OPEN")).isNotNull();
- assertThat(machine.state("OPEN").transition("resolve")).isNotNull();
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-
-public class StateTest {
-
- Transition t1 = Transition.builder("close").from("OPEN").to("CLOSED").build();
-
- @Test
- public void key_should_be_set() {
- try {
- new State("", new Transition[0]);
- fail();
- } catch (IllegalArgumentException e) {
- assertThat(e).hasMessage("State key must be set");
- }
- }
-
- @Test
- public void key_should_be_upper_case() {
- try {
- new State("close", new Transition[0]);
- fail();
- } catch (IllegalArgumentException e) {
- assertThat(e).hasMessage("State key must be upper-case");
- }
- }
-
- @Test
- public void no_duplicated_out_transitions() {
- try {
- new State("CLOSE", new Transition[] {t1, t1});
- fail();
- } catch (IllegalArgumentException e) {
- assertThat(e).hasMessage("Transition 'close' is declared several times from the originating state 'CLOSE'");
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import org.junit.Test;
-import org.sonar.api.issue.condition.Condition;
-import org.sonar.core.issue.DefaultIssue;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class TransitionTest {
-
- Condition condition1 = mock(Condition.class);
- Condition condition2 = mock(Condition.class);
- Function function1 = mock(Function.class);
- Function function2 = mock(Function.class);
-
- @Test
- public void test_builder() throws Exception {
- Transition transition = Transition.builder("close")
- .from("OPEN").to("CLOSED")
- .conditions(condition1, condition2)
- .functions(function1, function2)
- .build();
- assertThat(transition.key()).isEqualTo("close");
- assertThat(transition.from()).isEqualTo("OPEN");
- assertThat(transition.to()).isEqualTo("CLOSED");
- assertThat(transition.conditions()).containsOnly(condition1, condition2);
- assertThat(transition.functions()).containsOnly(function1, function2);
- assertThat(transition.automatic()).isFalse();
- }
-
- @Test
- public void test_simplest_transition() throws Exception {
- Transition transition = Transition.builder("close")
- .from("OPEN").to("CLOSED")
- .build();
- assertThat(transition.key()).isEqualTo("close");
- assertThat(transition.from()).isEqualTo("OPEN");
- assertThat(transition.to()).isEqualTo("CLOSED");
- assertThat(transition.conditions()).isEmpty();
- assertThat(transition.functions()).isEmpty();
- }
-
- @Test
- public void key_should_be_set() {
- try {
- Transition.builder("").from("OPEN").to("CLOSED").build();
- fail();
- } catch (Exception e) {
- assertThat(e).hasMessage("Transition key must be set");
- }
- }
-
- @Test
- public void key_should_be_lower_case() {
- try {
- Transition.builder("CLOSE").from("OPEN").to("CLOSED").build();
- fail();
- } catch (Exception e) {
- assertThat(e).hasMessage("Transition key must be lower-case");
- }
- }
-
- @Test
- public void originating_status_should_be_set() {
- try {
- Transition.builder("close").from("").to("CLOSED").build();
- fail();
- } catch (Exception e) {
- assertThat(e).hasMessage("Originating status must be set");
- }
- }
-
- @Test
- public void destination_status_should_be_set() {
- try {
- Transition.builder("close").from("OPEN").to("").build();
- fail();
- } catch (Exception e) {
- assertThat(e).hasMessage("Destination status must be set");
- }
- }
-
- @Test
- public void should_verify_conditions() {
- DefaultIssue issue = new DefaultIssue();
- Transition transition = Transition.builder("close")
- .from("OPEN").to("CLOSED")
- .conditions(condition1, condition2)
- .build();
-
- when(condition1.matches(issue)).thenReturn(true);
- when(condition2.matches(issue)).thenReturn(false);
- assertThat(transition.supports(issue)).isFalse();
-
- when(condition1.matches(issue)).thenReturn(true);
- when(condition2.matches(issue)).thenReturn(true);
- assertThat(transition.supports(issue)).isTrue();
- }
-
- @Test
- public void test_equals_and_hashCode() throws Exception {
- Transition t1 = Transition.create("resolve", "OPEN", "RESOLVED");
- Transition t2 = Transition.create("resolve", "REOPENED", "RESOLVED");
- Transition t3 = Transition.create("confirm", "OPEN", "CONFIRMED");
-
- assertThat(t1).isNotEqualTo(t2);
- assertThat(t1).isNotEqualTo(t3);
- assertThat(t1).isEqualTo(t1);
-
- assertThat(t1.hashCode()).isEqualTo(t1.hashCode());
- }
-
- @Test
- public void test_toString() throws Exception {
- Transition t1 = Transition.create("resolve", "OPEN", "RESOLVED");
- assertThat(t1.toString()).isEqualTo("OPEN->resolve->RESOLVED");
- }
-
- @Test
- public void test_automatic_transition() throws Exception {
- Transition transition = Transition.builder("close")
- .from("OPEN").to("CLOSED")
- .automatic()
- .build();
- assertThat(transition.automatic()).isTrue();
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.core.issue.workflow;
-
-import org.junit.Test;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.sonar.core.issue.workflow.UnsetAssignee.INSTANCE;
-
-public class UnsetAssigneeTest {
-
- @Test
- public void unassign() {
- Function.Context context = mock(Function.Context.class);
- INSTANCE.execute(context);
- verify(context, times(1)).setAssignee(null);
- }
-}