]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4326 Allow to add and remove tags on a rule
authorJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>
Tue, 21 Jan 2014 15:19:48 +0000 (16:19 +0100)
committerJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>
Tue, 21 Jan 2014 15:20:09 +0000 (16:20 +0100)
15 files changed:
sonar-core/src/main/java/org/sonar/core/rule/RuleTagDao.java
sonar-core/src/main/java/org/sonar/core/rule/RuleTagMapper.java
sonar-core/src/main/resources/org/sonar/core/rule/RuleTagMapper.xml
sonar-core/src/test/java/org/sonar/core/rule/RuleTagDaoTest.java [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/rule/RuleTagDaoTest/delete-result.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/rule/RuleTagDaoTest/insert-result.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/rule/RuleTagDaoTest/shared.xml [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileRuleOperations.java
sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfiles.java
sonar-server/src/main/webapp/WEB-INF/app/controllers/rules_configuration_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/views/rules_configuration/_rule.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/rules_configuration/_rule_tags.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/rules_configuration/_select_tags.html.erb [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfileRuleOperationsTest.java
sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfilesTest.java

index 7e5696ef77f7be9e5c6c0eaf418fb12143cca9be..275b73f841bcc33adf4577d086d332e7d15d96d6 100644 (file)
@@ -19,9 +19,9 @@
  */
 package org.sonar.core.rule;
 
-import org.sonar.core.persistence.MyBatis;
-import org.sonar.api.ServerExtension;
 import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.ServerExtension;
+import org.sonar.core.persistence.MyBatis;
 
 import java.util.List;
 
@@ -50,6 +50,7 @@ public class RuleTagDao implements ServerExtension {
     SqlSession session = myBatis.openSession();
     try {
       insert(newRuleTag, session);
+      session.commit();
     } finally {
       MyBatis.closeQuietly(session);
     }
@@ -63,6 +64,7 @@ public class RuleTagDao implements ServerExtension {
     SqlSession session = myBatis.openSession();
     try {
       delete(tagId, session);
+      session.commit();
     } finally {
       MyBatis.closeQuietly(session);
     }
@@ -71,4 +73,17 @@ public class RuleTagDao implements ServerExtension {
   public void delete(long tagId, SqlSession session) {
     session.getMapper(RuleTagMapper.class).delete(tagId);
   }
+
+  public Long selectId(String tag) {
+    SqlSession session = myBatis.openSession();
+    try {
+      return selectId(tag, session);
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  public Long selectId(String tag, SqlSession session) {
+    return session.getMapper(RuleTagMapper.class).selectId(tag);
+  }
 }
index 656313007aff6adcc083d61d572c0ae78d5a7bc1..5798e0da0da63f7116ac36aa86319e789690f147 100644 (file)
@@ -28,4 +28,6 @@ public interface RuleTagMapper {
   void insert(RuleTagDto newTag);
 
   void delete(Long tagId);
+
+  Long selectId(String tag);
 }
index e2c8b16fe52ba5f14c4840d4db4fda753640ab92..b2c0f2758f162b67eb29fc94dfa0ab1f07181700 100644 (file)
     DELETE FROM rule_tags WHERE id=#{tagId}
   </update>
 
+  <select id="selectId" resultType="Long" parameterType="String">
+    SELECT id
+    FROM rule_tags
+    WHERE tag=#{tag}
+  </select>
+
 </mapper>
 
diff --git a/sonar-core/src/test/java/org/sonar/core/rule/RuleTagDaoTest.java b/sonar-core/src/test/java/org/sonar/core/rule/RuleTagDaoTest.java
new file mode 100644 (file)
index 0000000..a26b1f2
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * 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.core.rule;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.core.persistence.AbstractDaoTestCase;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class RuleTagDaoTest extends AbstractDaoTestCase {
+
+  RuleTagDao dao;
+
+  @Before
+  public void createDao() {
+    dao = new RuleTagDao(getMyBatis());
+  }
+
+  @Test
+  public void should_select_all_tags() {
+    setupData("shared");
+
+    assertThat(dao.selectAll()).hasSize(3);
+  }
+
+  @Test
+  public void should_select_id() {
+    setupData("shared");
+
+    assertThat(dao.selectId("tag1")).isEqualTo(1L);
+    assertThat(dao.selectId("unknown")).isNull();
+  }
+
+  @Test
+  public void should_insert_tag() {
+    setupData("shared");
+
+    dao.insert(new RuleTagDto().setTag("tag4"));
+    checkTable("insert", "rule_tags");
+  }
+
+  @Test
+  public void should_delete_tag() {
+    setupData("shared");
+
+    dao.delete(1L);
+    checkTable("delete", "rule_tags");
+  }
+}
diff --git a/sonar-core/src/test/resources/org/sonar/core/rule/RuleTagDaoTest/delete-result.xml b/sonar-core/src/test/resources/org/sonar/core/rule/RuleTagDaoTest/delete-result.xml
new file mode 100644 (file)
index 0000000..8c9c2d0
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<dataset>
+
+  <rule_tags id="2" tag="tag2"/>
+  <rule_tags id="3" tag="tag3"/>
+
+</dataset>
diff --git a/sonar-core/src/test/resources/org/sonar/core/rule/RuleTagDaoTest/insert-result.xml b/sonar-core/src/test/resources/org/sonar/core/rule/RuleTagDaoTest/insert-result.xml
new file mode 100644 (file)
index 0000000..3b80513
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<dataset>
+
+  <rule_tags id="1" tag="tag1"/>
+  <rule_tags id="2" tag="tag2"/>
+  <rule_tags id="3" tag="tag3"/>
+  <rule_tags id="4" tag="tag4"/>
+
+</dataset>
diff --git a/sonar-core/src/test/resources/org/sonar/core/rule/RuleTagDaoTest/shared.xml b/sonar-core/src/test/resources/org/sonar/core/rule/RuleTagDaoTest/shared.xml
new file mode 100644 (file)
index 0000000..e708579
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<dataset>
+
+  <rule_tags id="1" tag="tag1"/>
+  <rule_tags id="2" tag="tag2"/>
+  <rule_tags id="3" tag="tag3"/>
+
+</dataset>
index 754a981b3e92093f8011813b44a34a6b608a3a06..9f9d267bcf4c7a6613fd7148575a156a1293e458 100644 (file)
@@ -24,6 +24,9 @@ import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.commons.lang.StringUtils;
 import org.apache.ibatis.session.SqlSession;
 import org.sonar.api.ServerComponent;
 import org.sonar.api.rule.Severity;
@@ -34,17 +37,16 @@ import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.core.persistence.MyBatis;
 import org.sonar.core.qualityprofile.db.ActiveRuleDao;
 import org.sonar.core.qualityprofile.db.ActiveRuleDto;
-import org.sonar.core.rule.RuleDao;
-import org.sonar.core.rule.RuleDto;
-import org.sonar.core.rule.RuleParamDto;
-import org.sonar.core.rule.RuleRuleTagDto;
+import org.sonar.core.rule.*;
 import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.rule.RuleRegistry;
 import org.sonar.server.user.UserSession;
 
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import static com.google.common.collect.Lists.newArrayList;
 
@@ -53,19 +55,21 @@ public class QProfileRuleOperations implements ServerComponent {
   private final MyBatis myBatis;
   private final ActiveRuleDao activeRuleDao;
   private final RuleDao ruleDao;
+  private final RuleTagDao ruleTagDao;
   private final RuleRegistry ruleRegistry;
 
   private final System2 system;
 
-  public QProfileRuleOperations(MyBatis myBatis, ActiveRuleDao activeRuleDao, RuleDao ruleDao, RuleRegistry ruleRegistry) {
-    this(myBatis, activeRuleDao, ruleDao, ruleRegistry, System2.INSTANCE);
+  public QProfileRuleOperations(MyBatis myBatis, ActiveRuleDao activeRuleDao, RuleDao ruleDao, RuleTagDao ruleTagDao, RuleRegistry ruleRegistry) {
+    this(myBatis, activeRuleDao, ruleDao, ruleTagDao, ruleRegistry, System2.INSTANCE);
   }
 
   @VisibleForTesting
-  QProfileRuleOperations(MyBatis myBatis, ActiveRuleDao activeRuleDao, RuleDao ruleDao, RuleRegistry ruleRegistry, System2 system) {
+  QProfileRuleOperations(MyBatis myBatis, ActiveRuleDao activeRuleDao, RuleDao ruleDao, RuleTagDao ruleTagDao, RuleRegistry ruleRegistry, System2 system) {
     this.myBatis = myBatis;
     this.activeRuleDao = activeRuleDao;
     this.ruleDao = ruleDao;
+    this.ruleTagDao = ruleTagDao;
     this.ruleRegistry = ruleRegistry;
     this.system = system;
   }
@@ -219,6 +223,57 @@ public class QProfileRuleOperations implements ServerComponent {
     }
   }
 
+  public void updateTags(RuleDto rule, List<String> newTags, UserSession userSession) {
+    checkPermission(userSession);
+    SqlSession session = myBatis.openSession();
+    try {
+      boolean ruleChanged = false;
+      Map<String, Long> neededTagIds = Maps.newHashMap();
+      Set<String> unknownTags = Sets.newHashSet();
+      for (String tag: newTags) {
+        Long tagId = ruleTagDao.selectId(tag, session);
+        if (tagId == null) {
+          unknownTags.add(tag);
+        } else {
+          neededTagIds.put(tag, tagId);
+        }
+      }
+      if (!unknownTags.isEmpty()) {
+        throw new NotFoundException("The following tags are unknown and must be created before association: "
+          + StringUtils.join(unknownTags, ", "));
+      }
+
+      Set<String> tagsToKeep = Sets.newHashSet();
+      final Integer ruleId = rule.getId();
+
+      List<RuleRuleTagDto> currentTags = ruleDao.selectTags(ruleId, session);
+      for (RuleRuleTagDto existingTag: currentTags) {
+        if(existingTag.getType() == RuleTagType.ADMIN && !newTags.contains(existingTag.getTag())) {
+          ruleDao.deleteTag(existingTag, session);
+          ruleChanged = true;
+        } else {
+          tagsToKeep.add(existingTag.getTag());
+        }
+      }
+
+      for (String tag: newTags) {
+        if (! tagsToKeep.contains(tag)) {
+          ruleDao.insert(new RuleRuleTagDto().setRuleId(ruleId).setTagId(neededTagIds.get(tag)).setType(RuleTagType.ADMIN), session);
+          ruleChanged = true;
+        }
+      }
+
+      if (ruleChanged) {
+        rule.setUpdatedAt(new Date(system.now()));
+        ruleDao.update(rule, session);
+        session.commit();
+        reindexRule(rule, session);
+      }
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
   private void reindexRule(RuleDto rule, SqlSession session) {
     reindexRule(rule, ruleDao.selectParameters(rule.getId(), session), ruleDao.selectTags(rule.getId(), session));
   }
@@ -243,5 +298,4 @@ public class QProfileRuleOperations implements ServerComponent {
   private static int getSeverityOrdinal(String severity) {
     return Severity.ALL.indexOf(severity);
   }
-
 }
index 8abc1dd1ef4dd9ba52a4aba0e82adcf692f29e04..6db47467ae3d238310ee10f87ddef7ab69e9f0dd 100644 (file)
@@ -20,6 +20,8 @@
 
 package org.sonar.server.qualityprofile;
 
+import com.google.common.collect.ImmutableList;
+
 import com.google.common.base.Strings;
 import org.sonar.api.ServerComponent;
 import org.sonar.api.component.Component;
@@ -39,6 +41,7 @@ import org.sonar.server.rule.ProfileRuleQuery;
 import org.sonar.server.rule.ProfileRules;
 import org.sonar.server.rule.RuleTagLookup;
 import org.sonar.server.user.UserSession;
+import org.sonar.server.util.RubyUtils;
 import org.sonar.server.util.Validation;
 
 import javax.annotation.CheckForNull;
@@ -375,6 +378,16 @@ public class QProfiles implements ServerComponent {
     return ruleTagLookup.listAllTags();
   }
 
+  public QProfileRule updateRuleTags(int ruleId, Object tags) {
+    RuleDto rule = findRuleNotNull(ruleId);
+    List<String> newTags = RubyUtils.toStrings(tags);
+    if (newTags == null) {
+      newTags = ImmutableList.of();
+    }
+    ruleOperations.updateTags(rule, newTags, UserSession.get());
+    return rules.findByRuleId(ruleId);
+  }
+
   //
   // Quality profile validation
   //
index 7d25064f006f5501926935723033d21c0901ac8c..622158d0f4447212ab76ceaee1b3d3da830ace32 100644 (file)
@@ -337,6 +337,23 @@ class RulesConfigurationController < ApplicationController
     render :partial => 'active_rule_note', :locals => {:rule => rule}
   end
 
+  def show_select_tags
+    rule = Internal.quality_profiles.findByRule(params[:rule_id].to_i)
+    tags = []
+    Internal.quality_profiles.listAllTags().sort.each do |tag|
+      tags.push({
+        :value => tag,
+        :selected => (rule.systemTags.contains?(tag) || rule.adminTags.contains?(tag)),
+        :read_only => rule.systemTags.contains?(tag)
+      })
+    end
+    render :partial => 'select_tags', :locals => { :rule => rule, :tags => tags, :profile_id => params[:profile_id] }
+  end
+
+  def select_tags
+    rule = Internal.quality_profiles.updateRuleTags(params[:rule_id].to_i, params[:tags])
+    render :partial => 'rule_tags', :locals => {:rule => rule}
+  end
 
   private
 
index e0f8f0bf3382ccf2f0975bbbf64db193eb9f8a64..6ad68ac712a87e99312a75e7f8f5215fc7106b98 100644 (file)
       <% end %>
     </div>
   <% end %>
-  <% unless rule.systemTags.isEmpty && rule.adminTags.isEmpty %>
-  <div class="rule-tags">
-    <% rule.systemTags.each do |tag| %>
-    <span id="rule_tag_<%= rule.id -%>_<%= tag -%>"><%= tag -%></span>
-    <% end %>
-    <% rule.adminTags.each do |tag| %>
-    <span id="rule_tag_<%= rule.id -%>_<%= tag -%>"><%= tag -%></span>
-    <% end %>
+  <div class="rule-tags" id="rule_tags_<%= rule.id %>">
+  <%= render :partial => 'rule_tags', :locals => {:rule => rule} -%>
   </div>
-  <% end %>
-
 
   <div class="h3 rule-title"><%= link_to_function("#{h rule.name}", nil, :class => "") do |page|
     page.toggle "desc_#{rule.id}"
       </table>
     <% end %>
 
+    <% if profiles_administrator? %>
+      <div id="open_rule_tags_<%= rule.id -%>">
+          <a class="link-action open-modal" href="<%=ApplicationController.root_context-%>/rules_configuration/show_select_tags?rule_id=<%=rule.id-%>&amp;profile_id=<%= u profile.id %>">Select Tags</a>
+      </div>
+    <% end %>
+
     <% if is_activated %>
       <div id="active_rule_note_<%= rule.activeRuleId -%>">
         <%= render :partial => 'active_rule_note', :locals => {:rule => rule} %>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/rules_configuration/_rule_tags.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/rules_configuration/_rule_tags.html.erb
new file mode 100644 (file)
index 0000000..13aba0f
--- /dev/null
@@ -0,0 +1,9 @@
+<% #locals = rule -%>
+<% unless rule.systemTags.isEmpty && rule.adminTags.isEmpty %>
+  <% rule.systemTags.each do |tag| %>
+  <span id="rule_tag_<%= rule.id -%>_<%= tag -%>"><%= tag -%></span>
+  <% end %>
+  <% rule.adminTags.each do |tag| %>
+  <span id="rule_tag_<%= rule.id -%>_<%= tag -%>"><%= tag -%></span>
+  <% end %>
+<% end %>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/rules_configuration/_select_tags.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/rules_configuration/_select_tags.html.erb
new file mode 100644 (file)
index 0000000..e58680e
--- /dev/null
@@ -0,0 +1,37 @@
+<% #locals = rule, tags, profile_id -%>
+<form onsubmit="$j.ajax({
+                        url:'<%=ApplicationController.root_context-%>/rules_configuration/select_tags',
+                        success:function(response){ closeModalWindow();$j('#rule_tags_<%=rule.id-%>').html(response); },
+                        error:function(response){ $j('#select-tags-error').html(response); },
+                        data: $j(this).serialize(),
+                        type:'post'});
+                  return false;"
+        method="post"
+        action="<%=ApplicationController.root_context-%>/rules_configuration/select_tags">
+  <%= hidden_field_tag :rule_id, rule.id -%>
+  <%= hidden_field_tag :profile_id, profile_id -%>
+  <fieldset>
+    <div class="modal-head">
+      <h2>Select Tags for Rule: <%= h rule.name -%></h2><%# TODO i18n?-%>
+    </div>
+
+    <div class="modal-body">
+      <div class="modal-error" id="select-tags-error"/>
+      <div id="select-rule-tags" style="height: 200px; overflow-y: visible">
+        <ul>
+        <% tags.each do |tag| -%>
+          <li style="padding: 5px 2px">
+            <%= check_box_tag 'tags[]', tag[:value], tag[:selected], :id => 'tags_'+tag[:value], :disabled => tag[:read_only] -%>
+            <label for="tags_<%= tag[:value] -%>"><%= tag[:value] -%></label>
+          </li>
+        <% end -%>
+        </ul>
+      </div>
+    </div>
+
+    <div class="modal-foot">
+      <%= submit_tag message('Update Tags'), { :id => 'update_tags_' + rule.id.to_s } -%>
+      <a href="#" onclick="return closeModalWindow()" id="confirm-cancel"><%= h message('cancel') -%></a>
+    </div>
+  </fieldset>
+</form>
index 22059a537484c5d0336f1ef091ac314accdbad66..6036e2f9655e18a4d98707995c9b31f24652dc61 100644 (file)
@@ -20,6 +20,7 @@
 
 package org.sonar.server.qualityprofile;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import org.apache.ibatis.session.SqlSession;
 import org.junit.Before;
@@ -41,6 +42,7 @@ import org.sonar.core.qualityprofile.db.ActiveRuleDao;
 import org.sonar.core.qualityprofile.db.ActiveRuleDto;
 import org.sonar.core.rule.*;
 import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.rule.RuleRegistry;
 import org.sonar.server.user.MockUserSession;
 import org.sonar.server.user.UserSession;
@@ -72,6 +74,9 @@ public class QProfileRuleOperationsTest {
   @Mock
   RuleDao ruleDao;
 
+  @Mock
+  RuleTagDao ruleTagDao;
+
   @Mock
   RuleRegistry ruleRegistry;
 
@@ -99,7 +104,7 @@ public class QProfileRuleOperationsTest {
       }
     }).when(activeRuleDao).insert(any(ActiveRuleDto.class), any(SqlSession.class));
 
-    operations = new QProfileRuleOperations(myBatis, activeRuleDao, ruleDao, ruleRegistry, system);
+    operations = new QProfileRuleOperations(myBatis, activeRuleDao, ruleDao, ruleTagDao, ruleRegistry, system);
   }
 
   @Test
@@ -283,4 +288,70 @@ public class QProfileRuleOperationsTest {
     verify(ruleRegistry).deleteActiveRules(newArrayList(activeRuleId));
   }
 
+  @Test(expected = ForbiddenException.class)
+  public void should_fail_update_tags_on_unauthorized_user() {
+    operations.updateTags(new RuleDto(), ImmutableList.of("polop"), unauthorizedUserSession);
+  }
+
+  @Test(expected = NotFoundException.class)
+  public void should_fail_update_tags_on_unknown_tag() {
+    final String tag = "polop";
+    when(ruleTagDao.selectId(tag, session)).thenReturn(null);
+    operations.updateTags(new RuleDto(), ImmutableList.of(tag), authorizedUserSession);
+  }
+
+  @Test
+  public void should_add_new_tags() {
+    final int ruleId = 24;
+    final RuleDto rule = new RuleDto().setId(ruleId);
+    final String tag = "polop";
+    final long tagId = 42L;
+    when(ruleTagDao.selectId(tag, session)).thenReturn(tagId);
+
+    operations.updateTags(rule, ImmutableList.of(tag), authorizedUserSession);
+
+    verify(ruleTagDao).selectId(tag, session);
+    ArgumentCaptor<RuleRuleTagDto> capture = ArgumentCaptor.forClass(RuleRuleTagDto.class);
+    verify(ruleDao).insert(capture.capture(), eq(session));
+    final RuleRuleTagDto newTag = capture.getValue();
+    assertThat(newTag.getRuleId()).isEqualTo(ruleId);
+    assertThat(newTag.getTagId()).isEqualTo(tagId);
+    assertThat(newTag.getType()).isEqualTo(RuleTagType.ADMIN);
+    verify(ruleDao).update(rule, session);
+    verify(session).commit();
+  }
+
+  @Test
+  public void should_delete_removed_tags() {
+    final int ruleId = 24;
+    final RuleDto rule = new RuleDto().setId(ruleId);
+    final String tag = "polop";
+    RuleRuleTagDto existingTag = new RuleRuleTagDto().setTag(tag).setType(RuleTagType.ADMIN);
+    when(ruleDao.selectTags(ruleId, session)).thenReturn(ImmutableList.of(existingTag));
+
+
+    operations.updateTags(rule, ImmutableList.<String>of(), authorizedUserSession);
+
+    verify(ruleDao, atLeast(1)).selectTags(ruleId, session);
+    verify(ruleDao).deleteTag(existingTag, session);
+    verify(ruleDao).update(rule, session);
+    verify(session).commit();
+  }
+
+  @Test
+  public void should_not_update_rule_if_tags_unchanged() {
+    final int ruleId = 24;
+    final RuleDto rule = new RuleDto().setId(ruleId);
+    final String tag = "polop";
+    final long tagId = 42L;
+    when(ruleTagDao.selectId(tag, session)).thenReturn(tagId);
+    RuleRuleTagDto existingTag = new RuleRuleTagDto().setTag(tag).setType(RuleTagType.ADMIN);
+    when(ruleDao.selectTags(ruleId, session)).thenReturn(ImmutableList.of(existingTag));
+
+    operations.updateTags(rule, ImmutableList.of(tag), authorizedUserSession);
+
+    verify(ruleTagDao).selectId(tag, session);
+    verify(ruleDao).selectTags(ruleId, session);
+    verify(ruleDao, never()).update(rule);
+  }
 }
index e93baef7a65e8b287b42314093f27181d17e43df..12a43f93b50ccfdbba7b663aeafa8cceee050b29 100644 (file)
@@ -20,6 +20,7 @@
 
 package org.sonar.server.qualityprofile;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import org.junit.Before;
@@ -43,16 +44,15 @@ import org.sonar.server.rule.ProfileRules;
 import org.sonar.server.rule.RuleTagLookup;
 import org.sonar.server.user.UserSession;
 
+import java.util.Collection;
+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.fest.assertions.Fail.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.*;
 import static org.mockito.Mockito.*;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -648,4 +648,32 @@ public class QProfilesTest {
     assertThat(qProfiles.countActiveRules(rule)).isEqualTo(2);
   }
 
+  @Test
+  public void should_find_all_tags() {
+    Collection<String> tags = ImmutableList.of("tag1", "tag2");
+    when(ruleTagLookup.listAllTags()).thenReturn(tags);
+    assertThat(qProfiles.listAllTags()).isEqualTo(tags);
+    verify(ruleTagLookup).listAllTags();
+  }
+
+  @Test
+  public void should_pass_tags_to_update() {
+    final int ruleId = 11;
+    RuleDto rule = new RuleDto().setId(ruleId).setRepositoryKey("squid").setRuleKey("XPath_1387869254").setParentId(10);
+    when(ruleDao.selectById(ruleId)).thenReturn(rule);
+
+    qProfiles.updateRuleTags(ruleId, null);
+    verify(ruleOperations).updateTags(eq(rule), isA(List.class), any(UserSession.class));
+  }
+
+  @Test
+  public void should_prepare_empty_tag_list() {
+    final int ruleId = 11;
+    RuleDto rule = new RuleDto().setId(ruleId).setRepositoryKey("squid").setRuleKey("XPath_1387869254").setParentId(10);
+    when(ruleDao.selectById(ruleId)).thenReturn(rule);
+
+    List<String> tags = ImmutableList.of("tag1", "tag2");
+    qProfiles.updateRuleTags(ruleId, tags);
+    verify(ruleOperations).updateTags(eq(rule), eq(tags), any(UserSession.class));
+  }
 }