]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3714 Add check in plan action that all issues are related to the action plan...
authorJulien Lancelot <julien.lancelot@gmail.com>
Tue, 25 Jun 2013 08:41:56 +0000 (10:41 +0200)
committerJulien Lancelot <julien.lancelot@gmail.com>
Tue, 25 Jun 2013 08:41:56 +0000 (10:41 +0200)
16 files changed:
sonar-core/src/main/java/org/sonar/core/resource/ResourceDao.java
sonar-server/src/main/java/org/sonar/server/issue/Action.java
sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java
sonar-server/src/main/java/org/sonar/server/issue/CommentAction.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/issue/DefaultIssueFinder.java
sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java
sonar-server/src/main/java/org/sonar/server/issue/IssueBulkChangeResult.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/issue/IssueBulkChangeService.java
sonar-server/src/main/java/org/sonar/server/issue/PlanAction.java
sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java
sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/platform/Platform.java
sonar-server/src/test/java/org/sonar/server/issue/AssignActionTest.java
sonar-server/src/test/java/org/sonar/server/issue/CommentActionTest.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/issue/IssueBulkChangeServiceTest.java
sonar-server/src/test/java/org/sonar/server/issue/PlanActionTest.java

index bcb7514322d25e60aacfe57c23ebe955976322da..5dfb4ec22aeae29905b80713ced008e9f14812af 100644 (file)
@@ -155,6 +155,7 @@ public class ResourceDao {
     }
   }
 
+  @CheckForNull
   public Component findByKey(String key) {
     ResourceDto resourceDto = getResource(ResourceQuery.create().setKey(key));
     return resourceDto != null ? toComponent(resourceDto) : null;
index eddfba173f879785de5878dd1096bb33c9e04649..ec56d59ec9602462a6a414c9a1b566990b9f4338 100644 (file)
@@ -71,7 +71,7 @@ public abstract class Action implements ServerComponent {
     return true;
   }
 
-  abstract boolean verify(Map<String, Object> properties, UserSession userSession);
+  abstract boolean verify(Map<String, Object> properties, List<Issue> issues, UserSession userSession);
 
   abstract boolean execute(Map<String, Object> properties, Context context);
 
index 04a0f82ecd9c02f3127bc71f73a94f8cf6dd9af8..15b35697b3c8b14451b07da7cc834902a04d5535 100644 (file)
 package org.sonar.server.issue;
 
 import org.sonar.api.ServerComponent;
+import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.condition.IsUnResolved;
 import org.sonar.api.issue.internal.DefaultIssue;
 import org.sonar.api.user.UserFinder;
 import org.sonar.server.user.UserSession;
 
+import java.util.List;
 import java.util.Map;
 
 
@@ -42,7 +44,7 @@ public class AssignAction extends Action implements ServerComponent {
   }
 
   @Override
-  public boolean verify(Map<String, Object> properties, UserSession userSession){
+  public boolean verify(Map<String, Object> properties, List<Issue> issues, UserSession userSession){
     String assignee = assignee(properties);
     if (assignee != null && userFinder.findByLogin(assignee) == null) {
       throw new IllegalArgumentException("Unknown user: " + assignee);
diff --git a/sonar-server/src/main/java/org/sonar/server/issue/CommentAction.java b/sonar-server/src/main/java/org/sonar/server/issue/CommentAction.java
new file mode 100644 (file)
index 0000000..da4c244
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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;
+
+import org.sonar.api.ServerComponent;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.condition.IsUnResolved;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.server.user.UserSession;
+
+import java.util.List;
+import java.util.Map;
+
+
+public class CommentAction extends Action implements ServerComponent {
+
+  public static final String COMMENT_ACTION_KEY = "comment";
+
+  public CommentAction() {
+    super(COMMENT_ACTION_KEY);
+    super.setConditions(new IsUnResolved());
+  }
+
+  @Override
+  public boolean verify(Map<String, Object> properties, List<Issue> issues, UserSession userSession) {
+    return true;
+  }
+
+  @Override
+  public boolean execute(Map<String, Object> properties, Context context) {
+    context.issueUpdater().addComment((DefaultIssue) context.issue(), comment(properties), context.issueChangeContext());
+    return true;
+  }
+
+  private String comment(Map<String, Object> properties) {
+    return (String) properties.get("comment");
+  }
+}
\ No newline at end of file
index 5fdd5b54e04a892b04cc112f2bf77e0809b7435c..2cd7a861fdb9914b50e4bb1e2a5e6978c7c4452d 100644 (file)
@@ -147,7 +147,7 @@ public class DefaultIssueFinder implements IssueFinder {
         .setMaxResultsReached(authorizedIssues.size() == query.maxResults())
         .addRules(findRules(ruleIds))
         .addComponents(findComponents(componentIds))
-        .addProjects(findProjects(projectIds))
+        .addProjects(findComponents(projectIds))
         .addActionPlans(findActionPlans(actionPlanKeys))
         .addUsers(findUsers(users))
         .setPaging(paging);
@@ -189,10 +189,6 @@ public class DefaultIssueFinder implements IssueFinder {
     return resourceDao.findByIds(componentIds);
   }
 
-  private Collection<Component> findProjects(Set<Long> projectIds) {
-    return resourceDao.findByIds(projectIds);
-  }
-
   private Collection<ActionPlan> findActionPlans(Set<String> actionPlanKeys) {
     return actionPlanService.findByKeys(actionPlanKeys);
   }
index a3edbcc346a6991d0d4c12290b17be3f5b4ed5aa..f25fbd45238b2c4d5ea6c046e3e9321ce80250e1 100644 (file)
@@ -588,8 +588,8 @@ public class InternalRubyIssueService implements ServerComponent {
   /**
    * Execute a bulk change
    */
-  public Result<List<Issue>> executeBulkChange(Map<String, Object> props) {
-    Result<List<Issue>> result = Result.of();
+  public Result<IssueBulkChangeResult> executeBulkChange(Map<String, Object> props) {
+    Result<IssueBulkChangeResult> result = Result.of();
     try {
       IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(props);
       result.set(issueBulkChangeService.execute(issueBulkChangeQuery, UserSession.get()));
diff --git a/sonar-server/src/main/java/org/sonar/server/issue/IssueBulkChangeResult.java b/sonar-server/src/main/java/org/sonar/server/issue/IssueBulkChangeResult.java
new file mode 100644 (file)
index 0000000..092f3bf
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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;
+
+import org.sonar.api.issue.Issue;
+
+import java.util.List;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+public class IssueBulkChangeResult {
+
+  List<Issue> issuesChanged = newArrayList();
+  List<Issue> issuesNotChanged = newArrayList();
+
+  public void addIssueChanged(Issue issue){
+    this.issuesChanged.add(issue);
+  }
+
+  public void addIssueNotChanged(Issue issue){
+    this.issuesNotChanged.add(issue);
+  }
+
+  public List<Issue> issuesChanged() {
+    return issuesChanged;
+  }
+
+  public List<Issue> issuesInError() {
+    return issuesNotChanged;
+  }
+}
index 836eb4774dcfa88f007a5f5dbad2736e956f9d90..67acadd059abc992463315737ddb6bbbae501241 100644 (file)
@@ -40,8 +40,6 @@ import javax.annotation.CheckForNull;
 import java.util.Date;
 import java.util.List;
 
-import static com.google.common.collect.Lists.newArrayList;
-
 public class IssueBulkChangeService {
 
   private static final Logger LOG = LoggerFactory.getLogger(IssueBulkChangeService.class);
@@ -60,18 +58,22 @@ public class IssueBulkChangeService {
     this.actions = actions;
   }
 
-  public List<Issue> execute(IssueBulkChangeQuery issueBulkChangeQuery, UserSession userSession) {
-    List<Issue> issues = newArrayList();
+  public IssueBulkChangeResult execute(IssueBulkChangeQuery issueBulkChangeQuery, UserSession userSession) {
     verifyLoggedIn(userSession);
 
+    IssueBulkChangeResult result = new IssueBulkChangeResult();
+    IssueQueryResult issueQueryResult = issueFinder.find(IssueQuery.builder().issueKeys(issueBulkChangeQuery.issues()).requiredRole(UserRole.USER).build());
+    List<Issue> issues = issueQueryResult.issues();
     for (String actionName : issueBulkChangeQuery.actions()) {
       Action action = getAction(actionName);
-      action.verify(issueBulkChangeQuery.properties(actionName), userSession);
+      if (action == null) {
+        throw new IllegalArgumentException("The action : '"+ actionName + "' is unknown");
+      }
+      action.verify(issueBulkChangeQuery.properties(actionName), issues, userSession);
     }
 
-    IssueQueryResult issueQueryResult = issueFinder.find(IssueQuery.builder().issueKeys(issueBulkChangeQuery.issues()).requiredRole(UserRole.USER).build());
     IssueChangeContext issueChangeContext = IssueChangeContext.createUser(new Date(), userSession.login());
-    for (Issue issue : issueQueryResult.issues()) {
+    for (Issue issue : issues) {
       for (String actionName : issueBulkChangeQuery.actions()) {
         try {
           Action action = getAction(actionName);
@@ -79,15 +81,17 @@ public class IssueBulkChangeService {
           if (action.supports(issue) && action.execute(issueBulkChangeQuery.properties(actionName), actionContext)) {
             issueStorage.save((DefaultIssue) issue);
             issueNotifications.sendChanges((DefaultIssue) issue, issueChangeContext, issueQueryResult);
-            issues.add(issue);
+            result.addIssueChanged(issue);
+          } else {
+            result.addIssueNotChanged(issue);
           }
         } catch (Exception e) {
-          // Do nothing, just go to the next issue
+          result.addIssueNotChanged(issue);
           LOG.info("An error occur when trying to apply the action : "+ actionName + " on issue : "+ issue.key() + ". This issue has been ignored.", e);
         }
       }
     }
-    return issues;
+    return result;
   }
 
   @CheckForNull
index c7d9433c199977391f8968831f0db17bc7191451..7c38b6c18c154d86e0ea8ac98eb7f1c00d169e4d 100644 (file)
@@ -22,10 +22,13 @@ package org.sonar.server.issue;
 
 import com.google.common.base.Strings;
 import org.sonar.api.ServerComponent;
+import org.sonar.api.issue.ActionPlan;
+import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.condition.IsUnResolved;
 import org.sonar.api.issue.internal.DefaultIssue;
 import org.sonar.server.user.UserSession;
 
+import java.util.List;
 import java.util.Map;
 
 
@@ -42,9 +45,15 @@ public class PlanAction extends Action implements ServerComponent {
   }
 
   @Override
-  public boolean verify(Map<String, Object> properties, UserSession userSession){
+  public boolean verify(Map<String, Object> properties, List<Issue> issues, UserSession userSession) {
     String actionPlanKey = planKey(properties);
-    if (!Strings.isNullOrEmpty(actionPlanKey) && actionPlanService.findByKey(actionPlanKey, userSession) == null) {
+    if (!Strings.isNullOrEmpty(actionPlanKey)) {
+      ActionPlan actionPlan = actionPlanService.findByKey(actionPlanKey, userSession);
+      if (actionPlan == null) {
+        throw new IllegalArgumentException("Unknown action plan: " + actionPlanKey);
+      }
+      verifyIssuesAreAllRelatedOnActionPlanProject(issues, actionPlan);
+    } else {
       throw new IllegalArgumentException("Unknown action plan: " + actionPlanKey);
     }
     return true;
@@ -56,8 +65,18 @@ public class PlanAction extends Action implements ServerComponent {
     return true;
   }
 
-  private String planKey(Map<String, Object> properties){
+  private String planKey(Map<String, Object> properties) {
     return (String) properties.get("plan");
   }
 
+  private void verifyIssuesAreAllRelatedOnActionPlanProject(List<Issue> issues, ActionPlan actionPlan) {
+    String projectKey = actionPlan.projectKey();
+    for (Issue issue : issues) {
+      DefaultIssue defaultIssue = (DefaultIssue) issue;
+      if (!defaultIssue.projectKey().equals(projectKey)) {
+        throw new IllegalArgumentException("Issues are not all related to the action plan project: " + projectKey);
+      }
+    }
+  }
+
 }
\ No newline at end of file
index 8f6ccb6c78ad6e165f801af61a6310238a1de6f8..e51501599e4fde230d02b16baea34bd82b7cabbb 100644 (file)
 package org.sonar.server.issue;
 
 import org.sonar.api.ServerComponent;
+import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.condition.IsUnResolved;
 import org.sonar.api.issue.internal.DefaultIssue;
 import org.sonar.server.user.UserSession;
 
+import java.util.List;
 import java.util.Map;
 
 
@@ -38,7 +40,7 @@ public class SetSeverityAction extends Action implements ServerComponent {
   }
 
   @Override
-  public boolean verify(Map<String, Object> properties, UserSession userSession) {
+  public boolean verify(Map<String, Object> properties, List<Issue> issues, UserSession userSession) {
     return true;
   }
 
diff --git a/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java b/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java
new file mode 100644 (file)
index 0000000..0379011
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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;
+
+import org.sonar.api.ServerComponent;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.condition.IsUnResolved;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.server.user.UserSession;
+
+import java.util.List;
+import java.util.Map;
+
+
+public class TransitionAction extends Action implements ServerComponent {
+
+  public static final String TRANSITION_ACTION_KEY = "transition";
+
+  public TransitionAction() {
+    super(TRANSITION_ACTION_KEY);
+    super.setConditions(new IsUnResolved());
+  }
+
+  @Override
+  public boolean verify(Map<String, Object> properties, List<Issue> issues, UserSession userSession) {
+    return true;
+  }
+
+  @Override
+  public boolean execute(Map<String, Object> properties, Context context) {
+    context.issueUpdater().addComment((DefaultIssue) context.issue(), transition(properties), context.issueChangeContext());
+    return true;
+  }
+
+  private String transition(Map<String, Object> properties) {
+    return (String) properties.get("transition");
+  }
+}
\ No newline at end of file
index 8968b3d06e0810ef20cf14963caeb1bcfa419479..9bdc3e50d99c6e1dec43b4ccf3b4153fead52838 100644 (file)
@@ -281,6 +281,7 @@ public final class Platform {
     servicesContainer.addSingleton(AssignAction.class);
     servicesContainer.addSingleton(PlanAction.class);
     servicesContainer.addSingleton(SetSeverityAction.class);
+    servicesContainer.addSingleton(CommentAction.class);
 
     // rules
     servicesContainer.addSingleton(RubyRuleService.class);
index 0d7b2af26e641b37b084ee3eb45629cdb581cace..98c3d68e476ff90be8f5435dd90160256f443612 100644 (file)
@@ -30,8 +30,10 @@ import org.sonar.core.issue.IssueUpdater;
 import org.sonar.core.user.DefaultUser;
 import org.sonar.server.user.UserSession;
 
+import java.util.List;
 import java.util.Map;
 
+import static com.google.common.collect.Lists.newArrayList;
 import static com.google.common.collect.Maps.newHashMap;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Matchers.any;
@@ -72,12 +74,21 @@ public class AssignActionTest {
     Map<String, Object> properties = newHashMap();
     properties.put("assignee", assignee);
 
+    List<Issue> issues = newArrayList((Issue) new DefaultIssue().setKey("ABC"));
     when(userFinder.findByLogin(assignee)).thenReturn(new DefaultUser());
-    assertThat(action.verify(properties, mock(UserSession.class))).isTrue();
+    assertThat(action.verify(properties, issues, mock(UserSession.class))).isTrue();
+  }
+
+  @Test
+  public void should_fail_if_assignee_does_not_exists(){
+    String assignee = "arthur";
+    Map<String, Object> properties = newHashMap();
+    properties.put("assignee", assignee);
 
+    List<Issue> issues = newArrayList((Issue) new DefaultIssue().setKey("ABC"));
     when(userFinder.findByLogin(assignee)).thenReturn(null);
     try {
-      action.verify(properties, mock(UserSession.class));
+      action.verify(properties, issues, mock(UserSession.class));
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("Unknown user: arthur");
     }
diff --git a/sonar-server/src/test/java/org/sonar/server/issue/CommentActionTest.java b/sonar-server/src/test/java/org/sonar/server/issue/CommentActionTest.java
new file mode 100644 (file)
index 0000000..247fe88
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
+
+import java.util.Map;
+
+import static com.google.common.collect.Maps.newHashMap;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.*;
+
+public class CommentActionTest {
+
+  private CommentAction action;
+
+  @Before
+  public void before(){
+    action = new CommentAction();
+  }
+
+  @Test
+  public void should_execute(){
+    String comment = "My bulk change comment";
+    Map<String, Object> properties = newHashMap();
+    properties.put("comment", comment);
+    DefaultIssue issue = mock(DefaultIssue.class);
+    IssueUpdater issueUpdater = mock(IssueUpdater.class);
+
+    Action.Context context = mock(Action.Context.class);
+    when(context.issueUpdater()).thenReturn(issueUpdater);
+    when(context.issue()).thenReturn(issue);
+
+    action.execute(properties, context);
+    verify(issueUpdater).addComment(eq(issue), eq(comment), any(IssueChangeContext.class));
+  }
+
+  @Test
+  public void should_support_only_unresolved_issues(){
+    assertThat(action.supports(new DefaultIssue().setStatus(Issue.STATUS_OPEN))).isTrue();
+    assertThat(action.supports(new DefaultIssue().setStatus(Issue.STATUS_CLOSED))).isTrue();
+  }
+
+}
index 85e04a983a3fbc5ed01700fa12549ec08234584f..7d6041daacf544db4f13ab314bd598fc39d69068 100644 (file)
@@ -87,8 +87,9 @@ public class IssueBulkChangeServiceTest {
     when(action.execute(eq(properties), any(IssueBulkChangeService.ActionContext.class))).thenReturn(true);
 
     IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties);
-    List<Issue> result = service.execute(issueBulkChangeQuery, userSession);
-    assertThat(result).hasSize(1);
+    IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession);
+    assertThat(result.issuesChanged).hasSize(1);
+    assertThat(result.issuesNotChanged).isEmpty();
 
     verifyNoMoreInteractions(issueUpdater);
     verify(issueStorage).save(eq(issue));
@@ -106,8 +107,9 @@ public class IssueBulkChangeServiceTest {
     when(action.supports(any(Issue.class))).thenReturn(false);
 
     IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties);
-    List<Issue> result = service.execute(issueBulkChangeQuery, userSession);
-    assertThat(result).isEmpty();
+    IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession);
+    assertThat(result.issuesChanged).isEmpty();
+    assertThat(result.issuesNotChanged).hasSize(1);
 
     verifyZeroInteractions(issueUpdater);
     verifyZeroInteractions(issueStorage);
@@ -124,8 +126,9 @@ public class IssueBulkChangeServiceTest {
     when(action.execute(anyMap(), any(IssueBulkChangeService.ActionContext.class))).thenReturn(false);
 
     IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties);
-    List<Issue> result = service.execute(issueBulkChangeQuery, userSession);
-    assertThat(result).isEmpty();
+    IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession);
+    assertThat(result.issuesChanged).isEmpty();
+    assertThat(result.issuesNotChanged).hasSize(1);
 
     verifyZeroInteractions(issueUpdater);
     verifyZeroInteractions(issueStorage);
@@ -142,8 +145,9 @@ public class IssueBulkChangeServiceTest {
     doThrow(new RuntimeException("Error")).when(action).execute(anyMap(), any(IssueBulkChangeService.ActionContext.class));
 
     IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties);
-    List<Issue> result = service.execute(issueBulkChangeQuery, userSession);
-    assertThat(result).isEmpty();
+    IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession);
+    assertThat(result.issuesChanged).isEmpty();
+    assertThat(result.issuesNotChanged).hasSize(1);
 
     verifyZeroInteractions(issueUpdater);
     verifyZeroInteractions(issueStorage);
@@ -151,7 +155,7 @@ public class IssueBulkChangeServiceTest {
   }
 
   @Test
-  public void should_fail_if_user_not_loggued() {
+  public void should_fail_if_user_not_logged() {
     when(userSession.isLoggedIn()).thenReturn(false);
 
     Map<String, Object> properties = newHashMap();
@@ -165,6 +169,22 @@ public class IssueBulkChangeServiceTest {
       assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("User is not logged in");
     }
     verifyZeroInteractions(issueUpdater);
+    verifyZeroInteractions(issueStorage);
+    verifyZeroInteractions(issueNotifications);
+  }
+
+  @Test
+  public void should_fail_if_action_not_found() {
+    Map<String, Object> properties = newHashMap();
+    properties.put("issues", "ABCD");
+    properties.put("actions", "unknown");
+    IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties);
+    try {
+      service.execute(issueBulkChangeQuery, userSession);
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("The action : 'unknown' is unknown");
+    }
     verifyZeroInteractions(issueUpdater);
     verifyZeroInteractions(issueStorage);
     verifyZeroInteractions(issueNotifications);
index 31c6392136d3af0f106b8850389500486038da78..d610146a2210520f44fac0bc987eafa2d6cd6b50 100644 (file)
@@ -29,8 +29,10 @@ import org.sonar.core.issue.DefaultActionPlan;
 import org.sonar.core.issue.IssueUpdater;
 import org.sonar.server.user.UserSession;
 
+import java.util.List;
 import java.util.Map;
 
+import static com.google.common.collect.Lists.newArrayList;
 import static com.google.common.collect.Maps.newHashMap;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Matchers.any;
@@ -65,22 +67,46 @@ public class PlanActionTest {
   }
 
   @Test
-  public void should_verify_action_plan_exists(){
+  public void should_verify(){
     String planKey = "ABCD";
     Map<String, Object> properties = newHashMap();
     properties.put("plan", planKey);
 
-    when(actionPlanService.findByKey(eq(planKey), any(UserSession.class))).thenReturn(new DefaultActionPlan());
-    assertThat(action.verify(properties, mock(UserSession.class))).isTrue();
+    List<Issue> issues = newArrayList((Issue) new DefaultIssue().setKey("ABC").setProjectKey("struts"));
+    when(actionPlanService.findByKey(eq(planKey), any(UserSession.class))).thenReturn(new DefaultActionPlan().setProjectKey("struts"));
+    assertThat(action.verify(properties, issues, mock(UserSession.class))).isTrue();
+  }
+
+  @Test
+  public void should_fail_if_action_plan_does_not_exists(){
+    String planKey = "ABCD";
+    Map<String, Object> properties = newHashMap();
+    properties.put("plan", planKey);
 
+    List<Issue> issues = newArrayList((Issue) new DefaultIssue().setKey("ABC").setProjectKey("struts"));
     when(actionPlanService.findByKey(eq(planKey), any(UserSession.class))).thenReturn(null);
     try {
-      action.verify(properties, mock(UserSession.class));
+      action.verify(properties, issues, mock(UserSession.class));
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("Unknown action plan: ABCD");
     }
   }
 
+  @Test
+  public void should_verify_issues_are_on_the_same_project(){
+    String planKey = "ABCD";
+    Map<String, Object> properties = newHashMap();
+    properties.put("plan", planKey);
+
+    when(actionPlanService.findByKey(eq(planKey), any(UserSession.class))).thenReturn(new DefaultActionPlan().setProjectKey("struts"));
+    List<Issue> issues = newArrayList(new DefaultIssue().setKey("ABC").setProjectKey("struts"), (Issue) new DefaultIssue().setKey("ABE").setProjectKey("mybatis"));
+    try {
+      action.verify(properties, issues, mock(UserSession.class));
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("Issues are not all related to the action plan project: struts");
+    }
+  }
+
   @Test
   public void should_support_only_unresolved_issues(){
     assertThat(action.supports(new DefaultIssue().setStatus(Issue.STATUS_OPEN))).isTrue();