]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5285 Create /api/coverage/show, returning only hits for the moment
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 14 May 2014 08:18:24 +0000 (10:18 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 14 May 2014 08:18:24 +0000 (10:18 +0200)
28 files changed:
sonar-core/src/main/java/org/sonar/core/measure/db/MeasureDataDto.java
sonar-core/src/main/resources/org/sonar/core/measure/db/MeasureDataMapper.xml
sonar-core/src/test/java/org/sonar/core/measure/db/MeasureDataDaoTest.java
sonar-core/src/test/resources/org/sonar/core/measure/db/MeasureDataDaoTest/find_by_component_key_and_metric_key_without_text.xml [deleted file]
sonar-core/src/test/resources/org/sonar/core/measure/db/MeasureDataDaoTest/shared.xml
sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
sonar-server/src/main/java/org/sonar/server/source/SourceService.java
sonar-server/src/main/java/org/sonar/server/source/ws/ScmAction.java
sonar-server/src/main/java/org/sonar/server/source/ws/ScmWriter.java
sonar-server/src/main/java/org/sonar/server/source/ws/ShowAction.java
sonar-server/src/main/java/org/sonar/server/test/CoverageService.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/test/ws/CoverageShowAction.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/test/ws/CoverageWs.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/test/ws/TestsWs.java
sonar-server/src/main/java/org/sonar/server/user/UserSession.java
sonar-server/src/main/resources/org/sonar/server/test/ws/coverage-example-show.json [new file with mode: 0644]
sonar-server/src/main/resources/org/sonar/server/test/ws/example-plan.json [deleted file]
sonar-server/src/main/resources/org/sonar/server/test/ws/example-testable.json [deleted file]
sonar-server/src/main/resources/org/sonar/server/test/ws/tests-example-plan.json [new file with mode: 0644]
sonar-server/src/main/resources/org/sonar/server/test/ws/tests-example-testable.json [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/source/SourceServiceTest.java
sonar-server/src/test/java/org/sonar/server/test/CoverageServiceTest.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/test/ws/CoverageShowActionTest.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/test/ws/CoverageWsTest.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/user/MockUserSession.java
sonar-server/src/test/java/org/sonar/server/user/UserSessionTest.java
sonar-server/src/test/resources/org/sonar/server/test/ws/CoverageShowActionTest/show_coverage.json [new file with mode: 0644]
sonar-server/src/test/resources/org/sonar/server/test/ws/CoverageShowActionTest/show_coverage_with_from_and_to.json [new file with mode: 0644]

index c2003a9796eeaf937faac45f3ca22dc0acdd5de0..0ddb30f51ac5b48f6fbc80af88bf786d1552c3d7 100644 (file)
@@ -30,6 +30,8 @@ public class MeasureDataDto {
 
   private Integer snapshotId;
 
+  private String textValue;
+
   private byte[] data;
 
   public Integer getId() {
@@ -50,20 +52,16 @@ public class MeasureDataDto {
     return this;
   }
 
-  public byte[] getData() {
-    return data;
-  }
-
   public MeasureDataDto setData(byte[] data) {
     this.data = data;
     return this;
   }
 
   @CheckForNull
-  public String getText() {
+  public String getData() {
     if (data != null) {
       return new String(data, Charsets.UTF_8);
     }
-    return null;
+    return textValue;
   }
 }
index b908ad21cd14369ef65c4386134795561e0ccadb..9221a58f9e258696a74067c654bc1653f7f3da3d 100644 (file)
@@ -6,6 +6,7 @@
   <sql id="measureDataColumns">
     pm.id,
     pm.snapshot_id as snapshotId,
+    pm.text_value as textValue,
     pm.measure_data as data
   </sql>
 
index 507facc96db5a8420614984921b2b0854a07e058..81e3f053a3b2669a81ebf9324104072e9f289979 100644 (file)
@@ -36,26 +36,25 @@ public class MeasureDataDaoTest extends AbstractDaoTestCase {
   }
 
   @Test
-  public void find_by_component_key_and_metric_key() throws Exception {
+  public void find_data_by_component_key_and_metric_key() throws Exception {
     setupData("shared");
 
     MeasureDataDto result = dao.findByComponentKeyAndMetricKey("org.sonar.core.measure.db.MeasureData", "authors_by_line");
     assertThat(result.getId()).isEqualTo(20);
     assertThat(result.getSnapshotId()).isEqualTo(5);
-    assertThat(result.getText()).isNotNull();
     assertThat(result.getData()).isNotNull();
 
-    assertThat(result.getText()).isEqualTo("0123456789012345678901234567890123456789");
+    assertThat(result.getData()).isEqualTo("0123456789012345678901234567890123456789");
   }
 
   @Test
-  public void find_by_component_key_and_metric_key_without_text() throws Exception {
-    setupData("find_by_component_key_and_metric_key_without_text");
+  public void find_text_value_by_component_key_and_metric_key() throws Exception {
+    setupData("shared");
 
-    MeasureDataDto result = dao.findByComponentKeyAndMetricKey("org.sonar.core.measure.db.MeasureData", "authors_by_line");
-    assertThat(result.getId()).isEqualTo(20);
+    MeasureDataDto result = dao.findByComponentKeyAndMetricKey("org.sonar.core.measure.db.MeasureData", "coverage_line_hits_data");
+    assertThat(result.getId()).isEqualTo(21);
     assertThat(result.getSnapshotId()).isEqualTo(5);
-    assertThat(result.getText()).isNull();
-    assertThat(result.getData()).isNull();
+    assertThat(result.getData()).isEqualTo("36=1;37=1;38=1;39=1;43=1;48=1;53=1");
   }
 }
+
diff --git a/sonar-core/src/test/resources/org/sonar/core/measure/db/MeasureDataDaoTest/find_by_component_key_and_metric_key_without_text.xml b/sonar-core/src/test/resources/org/sonar/core/measure/db/MeasureDataDaoTest/find_by_component_key_and_metric_key_without_text.xml
deleted file mode 100644 (file)
index a4092fd..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<dataset>
-
-  <metrics id="10" name="authors_by_line"/>
-  <projects id="1" kee="org.sonar.core.measure.db.MeasureData" enabled="[true]"/>
-  <snapshots id="5" project_id="1" islast="[true]" />
-
-  <project_measures id="20" snapshot_id="5" metric_id="10" measure_data="[null]"/>
-
-</dataset>
index 57fb0e395d12969d0506d3eeccde6b70cce2219c..4c5b08efefbec2049c62f4768578fe8c42491083 100644 (file)
@@ -1,9 +1,13 @@
 <dataset>
 
   <metrics id="10" name="authors_by_line"/>
+  <metrics id="11" name="coverage_line_hits_data"/>
+
   <projects id="1" kee="org.sonar.core.measure.db.MeasureData" enabled="[true]"/>
+
   <snapshots id="5" project_id="1" islast="[true]" />
 
-  <project_measures id="20" snapshot_id="5" metric_id="10" measure_data="MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OQ=="/>
+  <project_measures id="20" snapshot_id="5" metric_id="10" text_value="[null]" measure_data="MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OQ=="/>
+  <project_measures id="21" snapshot_id="5" metric_id="11" text_value="36=1;37=1;38=1;39=1;43=1;48=1;53=1" measure_data="[null]"/>
 
 </dataset>
index 7c2d6cad3e1f96a82ed65f2c428c7f565d5bd55a..50341dc206a4e1f566b68efd4883a276149d7a3b 100644 (file)
@@ -55,13 +55,7 @@ import org.sonar.core.measure.MeasureFilterFactory;
 import org.sonar.core.metric.DefaultMetricFinder;
 import org.sonar.core.notification.DefaultNotificationManager;
 import org.sonar.core.permission.PermissionFacade;
-import org.sonar.core.persistence.DaoUtils;
-import org.sonar.core.persistence.DatabaseVersion;
-import org.sonar.core.persistence.DefaultDatabase;
-import org.sonar.core.persistence.MyBatis;
-import org.sonar.core.persistence.PreviewDatabaseFactory;
-import org.sonar.core.persistence.SemaphoreUpdater;
-import org.sonar.core.persistence.SemaphoresImpl;
+import org.sonar.core.persistence.*;
 import org.sonar.core.preview.PreviewCache;
 import org.sonar.core.profiling.Profiling;
 import org.sonar.core.purge.PurgeProfiler;
@@ -95,32 +89,10 @@ import org.sonar.server.db.DbClient;
 import org.sonar.server.db.EmbeddedDatabaseFactory;
 import org.sonar.server.db.migrations.DatabaseMigrations;
 import org.sonar.server.db.migrations.DatabaseMigrator;
-import org.sonar.server.debt.DebtCharacteristicsXMLImporter;
-import org.sonar.server.debt.DebtModelBackup;
-import org.sonar.server.debt.DebtModelLookup;
-import org.sonar.server.debt.DebtModelOperations;
-import org.sonar.server.debt.DebtModelPluginRepository;
-import org.sonar.server.debt.DebtModelService;
-import org.sonar.server.debt.DebtModelXMLExporter;
-import org.sonar.server.debt.DebtRulesXMLImporter;
+import org.sonar.server.debt.*;
 import org.sonar.server.es.ESIndex;
 import org.sonar.server.es.ESNode;
-import org.sonar.server.issue.ActionService;
-import org.sonar.server.issue.AssignAction;
-import org.sonar.server.issue.CommentAction;
-import org.sonar.server.issue.DefaultIssueFinder;
-import org.sonar.server.issue.InternalRubyIssueService;
-import org.sonar.server.issue.IssueBulkChangeService;
-import org.sonar.server.issue.IssueChangelogFormatter;
-import org.sonar.server.issue.IssueChangelogService;
-import org.sonar.server.issue.IssueCommentService;
-import org.sonar.server.issue.IssueService;
-import org.sonar.server.issue.IssueStatsFinder;
-import org.sonar.server.issue.PlanAction;
-import org.sonar.server.issue.PublicRubyIssueService;
-import org.sonar.server.issue.ServerIssueStorage;
-import org.sonar.server.issue.SetSeverityAction;
-import org.sonar.server.issue.TransitionAction;
+import org.sonar.server.issue.*;
 import org.sonar.server.issue.actionplan.ActionPlanService;
 import org.sonar.server.issue.actionplan.ActionPlanWs;
 import org.sonar.server.issue.filter.IssueFilterService;
@@ -138,49 +110,13 @@ import org.sonar.server.permission.ws.PermissionsWs;
 import org.sonar.server.platform.ws.RestartHandler;
 import org.sonar.server.platform.ws.ServerWs;
 import org.sonar.server.platform.ws.SystemWs;
-import org.sonar.server.plugins.BatchWs;
-import org.sonar.server.plugins.InstalledPluginReferentialFactory;
-import org.sonar.server.plugins.PluginDownloader;
-import org.sonar.server.plugins.ServerExtensionInstaller;
-import org.sonar.server.plugins.ServerPluginJarInstaller;
-import org.sonar.server.plugins.ServerPluginJarsInstaller;
-import org.sonar.server.plugins.ServerPluginRepository;
-import org.sonar.server.plugins.UpdateCenterClient;
-import org.sonar.server.plugins.UpdateCenterMatrixFactory;
+import org.sonar.server.plugins.*;
 import org.sonar.server.qualitygate.QgateProjectFinder;
 import org.sonar.server.qualitygate.QualityGates;
 import org.sonar.server.qualitygate.RegisterQualityGates;
-import org.sonar.server.qualitygate.ws.QGatesAppAction;
-import org.sonar.server.qualitygate.ws.QGatesCopyAction;
-import org.sonar.server.qualitygate.ws.QGatesCreateAction;
-import org.sonar.server.qualitygate.ws.QGatesCreateConditionAction;
-import org.sonar.server.qualitygate.ws.QGatesDeleteConditionAction;
-import org.sonar.server.qualitygate.ws.QGatesDeselectAction;
-import org.sonar.server.qualitygate.ws.QGatesDestroyAction;
-import org.sonar.server.qualitygate.ws.QGatesListAction;
-import org.sonar.server.qualitygate.ws.QGatesRenameAction;
-import org.sonar.server.qualitygate.ws.QGatesSearchAction;
-import org.sonar.server.qualitygate.ws.QGatesSelectAction;
-import org.sonar.server.qualitygate.ws.QGatesSetAsDefaultAction;
-import org.sonar.server.qualitygate.ws.QGatesShowAction;
-import org.sonar.server.qualitygate.ws.QGatesUnsetDefaultAction;
-import org.sonar.server.qualitygate.ws.QGatesUpdateConditionAction;
-import org.sonar.server.qualitygate.ws.QGatesWs;
-import org.sonar.server.qualityprofile.ActiveRuleService;
-import org.sonar.server.qualityprofile.DefaultProfilesCache;
-import org.sonar.server.qualityprofile.ESActiveRule;
-import org.sonar.server.qualityprofile.ProfilesManager;
-import org.sonar.server.qualityprofile.QProfileActiveRuleOperations;
-import org.sonar.server.qualityprofile.QProfileBackup;
-import org.sonar.server.qualityprofile.QProfileLookup;
-import org.sonar.server.qualityprofile.QProfileOperations;
-import org.sonar.server.qualityprofile.QProfileProjectLookup;
-import org.sonar.server.qualityprofile.QProfileProjectOperations;
-import org.sonar.server.qualityprofile.QProfileRepositoryExporter;
-import org.sonar.server.qualityprofile.QProfileRuleLookup;
-import org.sonar.server.qualityprofile.QProfiles;
+import org.sonar.server.qualitygate.ws.*;
+import org.sonar.server.qualityprofile.*;
 import org.sonar.server.qualityprofile.RegisterQualityProfiles;
-import org.sonar.server.qualityprofile.RuleActivationContextFactory;
 import org.sonar.server.qualityprofile.index.ActiveRuleIndex;
 import org.sonar.server.qualityprofile.index.ActiveRuleNormalizer;
 import org.sonar.server.qualityprofile.persistence.ActiveRuleDao;
@@ -188,23 +124,8 @@ import org.sonar.server.qualityprofile.ws.ProfilesWs;
 import org.sonar.server.qualityprofile.ws.QProfileRecreateBuiltInAction;
 import org.sonar.server.qualityprofile.ws.QProfilesWs;
 import org.sonar.server.qualityprofile.ws.RuleActivationActions;
-import org.sonar.server.rule.DeprecatedRulesDefinition;
-import org.sonar.server.rule.ESRuleTags;
-import org.sonar.server.rule.RubyRuleService;
-import org.sonar.server.rule.RuleDefinitionsLoader;
-import org.sonar.server.rule.RuleOperations;
-import org.sonar.server.rule.RuleRegistry;
-import org.sonar.server.rule.RuleRepositories;
-import org.sonar.server.rule.RuleTagLookup;
-import org.sonar.server.rule.RuleTagOperations;
-import org.sonar.server.rule.RuleTags;
-import org.sonar.server.rule.Rules;
-import org.sonar.server.rule.ws.AddTagsWsHandler;
-import org.sonar.server.rule.ws.RemoveTagsWsHandler;
-import org.sonar.server.rule.ws.RuleSearchWsHandler;
-import org.sonar.server.rule.ws.RuleShowWsHandler;
-import org.sonar.server.rule.ws.RuleTagsWs;
-import org.sonar.server.rule.ws.RulesWs;
+import org.sonar.server.rule.*;
+import org.sonar.server.rule.ws.*;
 import org.sonar.server.rule2.RegisterRules;
 import org.sonar.server.rule2.RuleService;
 import org.sonar.server.rule2.index.RuleIndex;
@@ -214,7 +135,6 @@ import org.sonar.server.rule2.ws.RulesWebService;
 import org.sonar.server.rule2.ws.SearchAction;
 import org.sonar.server.rule2.ws.SetTagsAction;
 import org.sonar.server.rule2.ws.TagsAction;
-import org.sonar.server.search.IndexUtils;
 import org.sonar.server.source.CodeColorizers;
 import org.sonar.server.source.DeprecatedSourceDecorator;
 import org.sonar.server.source.HtmlSourceDecorator;
@@ -223,20 +143,10 @@ import org.sonar.server.source.ws.ScmAction;
 import org.sonar.server.source.ws.ScmWriter;
 import org.sonar.server.source.ws.ShowAction;
 import org.sonar.server.source.ws.SourcesWs;
-import org.sonar.server.startup.CleanPreviewAnalysisCache;
-import org.sonar.server.startup.CopyRequirementsFromCharacteristicsToRules;
-import org.sonar.server.startup.GeneratePluginIndex;
-import org.sonar.server.startup.GwtPublisher;
-import org.sonar.server.startup.JdbcDriverDeployer;
-import org.sonar.server.startup.LogServerId;
-import org.sonar.server.startup.RegisterDashboards;
-import org.sonar.server.startup.RegisterDebtModel;
-import org.sonar.server.startup.RegisterMetrics;
-import org.sonar.server.startup.RegisterNewMeasureFilters;
-import org.sonar.server.startup.RegisterPermissionTemplates;
-import org.sonar.server.startup.RegisterServletFilters;
-import org.sonar.server.startup.RenameDeprecatedPropertyKeys;
-import org.sonar.server.startup.ServerMetadataPersister;
+import org.sonar.server.startup.*;
+import org.sonar.server.test.CoverageService;
+import org.sonar.server.test.ws.CoverageShowAction;
+import org.sonar.server.test.ws.CoverageWs;
 import org.sonar.server.test.ws.TestsWs;
 import org.sonar.server.text.MacroInterpreter;
 import org.sonar.server.text.RubyTextService;
@@ -245,20 +155,9 @@ import org.sonar.server.ui.JRubyProfiling;
 import org.sonar.server.ui.PageDecorations;
 import org.sonar.server.ui.Views;
 import org.sonar.server.updatecenter.ws.UpdateCenterWs;
-import org.sonar.server.user.DefaultUserService;
-import org.sonar.server.user.DoPrivileged;
-import org.sonar.server.user.GroupMembershipFinder;
-import org.sonar.server.user.GroupMembershipService;
-import org.sonar.server.user.NewUserNotifier;
-import org.sonar.server.user.SecurityRealmFactory;
+import org.sonar.server.user.*;
 import org.sonar.server.user.ws.UsersWs;
-import org.sonar.server.util.BooleanTypeValidation;
-import org.sonar.server.util.FloatTypeValidation;
-import org.sonar.server.util.IntegerTypeValidation;
-import org.sonar.server.util.StringListTypeValidation;
-import org.sonar.server.util.StringTypeValidation;
-import org.sonar.server.util.TextTypeValidation;
-import org.sonar.server.util.TypeValidations;
+import org.sonar.server.util.*;
 import org.sonar.server.ws.ListingWs;
 import org.sonar.server.ws.WebServiceEngine;
 
@@ -585,6 +484,9 @@ class ServerComponents {
 
     // Tests
     pico.addSingleton(TestsWs.class);
+    pico.addSingleton(CoverageService.class);
+    pico.addSingleton(CoverageWs.class);
+    pico.addSingleton(CoverageShowAction.class);
 
     // graphs and perspective related classes
     pico.addSingleton(TestablePerspectiveLoader.class);
index e081b1cce793bc56db6abd20c79d81f324e2d955..ad116a80cf201fbe225a3b14cc24be047dc8bd79 100644 (file)
@@ -25,13 +25,11 @@ import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.web.UserRole;
 import org.sonar.core.measure.db.MeasureDataDao;
 import org.sonar.core.measure.db.MeasureDataDto;
-import org.sonar.core.resource.ResourceDao;
-import org.sonar.core.resource.ResourceDto;
-import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.user.UserSession;
 
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
+
 import java.util.List;
 
 public class SourceService implements ServerComponent {
@@ -43,13 +41,11 @@ public class SourceService implements ServerComponent {
    */
   private final DeprecatedSourceDecorator deprecatedSourceDecorator;
 
-  private final ResourceDao resourceDao;
   private final MeasureDataDao measureDataDao;
 
-  public SourceService(HtmlSourceDecorator sourceDecorator, DeprecatedSourceDecorator deprecatedSourceDecorator, ResourceDao resourceDao, MeasureDataDao measureDataDao) {
+  public SourceService(HtmlSourceDecorator sourceDecorator, DeprecatedSourceDecorator deprecatedSourceDecorator, MeasureDataDao measureDataDao) {
     this.sourceDecorator = sourceDecorator;
     this.deprecatedSourceDecorator = deprecatedSourceDecorator;
-    this.resourceDao = resourceDao;
     this.measureDataDao = measureDataDao;
   }
 
@@ -68,11 +64,7 @@ public class SourceService implements ServerComponent {
   }
 
   public void checkPermission(String fileKey) {
-    ResourceDto project = resourceDao.getRootProjectByComponentKey(fileKey);
-    if (project == null) {
-      throw new NotFoundException("File does not exist");
-    }
-    UserSession.get().checkProjectPermission(UserRole.CODEVIEWER, project.getKey());
+    UserSession.get().checkComponentPermission(UserRole.CODEVIEWER, fileKey);
   }
 
   /**
@@ -95,7 +87,7 @@ public class SourceService implements ServerComponent {
   private String findDataFromComponent(String fileKey, String metricKey) {
     MeasureDataDto data = measureDataDao.findByComponentKeyAndMetricKey(fileKey, metricKey);
     if (data != null) {
-      return data.getText();
+      return data.getData();
     }
     return null;
   }
index 01e0976b338f61c0dca687c41331ffc94738795f..bfc688bb4111a7bee3620da3ffad0ec99a2fe466 100644 (file)
@@ -40,7 +40,7 @@ public class ScmAction implements RequestHandler {
 
   void define(WebService.NewController controller) {
     WebService.NewAction action = controller.createAction("scm")
-      .setDescription("Get SCM information of source files")
+      .setDescription("Get SCM information of source files. Require Browse permission on file's project")
       .setSince("4.4")
       .setResponseExample(Resources.getResource(getClass(), "example-scm.json"))
       .setHandler(this);
@@ -53,7 +53,7 @@ public class ScmAction implements RequestHandler {
 
     action
       .createParam("from")
-      .setDescription("First line to return. Starts at 1.")
+      .setDescription("First line to return. Starts at 1")
       .setExampleValue("10")
       .setDefaultValue("1");
 
index 38137894c007507d454b8970d0666a394eda1635..c3be42b4ad792eee8da30e78212f312b893f3f5e 100644 (file)
  */
 package org.sonar.server.source.ws;
 
-import com.google.common.base.Splitter;
 import org.sonar.api.ServerComponent;
 import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.KeyValueFormat;
 import org.sonar.api.utils.text.JsonWriter;
 
 import javax.annotation.Nullable;
 
-import java.util.Collections;
-import java.util.List;
-
-import static com.google.common.collect.Lists.newArrayList;
+import java.util.Map;
 
 public class ScmWriter implements ServerComponent {
 
-  void write(@Nullable String authorsData, @Nullable String datesDate, int from, int to, boolean group, JsonWriter json) {
+  void write(@Nullable String authorsData, @Nullable String datesData, int from, int to, boolean group, JsonWriter json) {
     json.name("scm").beginArray();
     if (authorsData != null) {
-      List<String> authors = splitLine(authorsData);
-      List<String> dates = splitLine(datesDate);
+      Map<Integer, String> authors = KeyValueFormat.parseIntString(authorsData);
+      Map<Integer, String> dates = KeyValueFormat.parseIntString(datesData);
 
       String previousAuthor = null;
       String previousDate = null;
       boolean started = false;
-      for (int i = 0; i < authors.size(); i++) {
-        String[] authorWithLine = splitColumn(authors.get(i));
-        Integer line = Integer.parseInt(authorWithLine[0]);
-        String author = authorWithLine[1];
-
-        String[] dateWithLine = splitColumn(dates.get(i));
-        String date = dateWithLine[1];
+      for (Map.Entry<Integer, String> entry : authors.entrySet()) {
+        Integer line = entry.getKey();
+        String author = entry.getValue();
+        String date = dates.get(line);
         String formattedDate = DateUtils.formatDate(DateUtils.parseDateTime(date));
         if (line >= from && line <= to) {
           if (!started || !group || !isSameCommit(date, previousDate, author, previousAuthor)) {
@@ -67,17 +61,6 @@ public class ScmWriter implements ServerComponent {
     json.endArray();
   }
 
-  private List<String> splitLine(@Nullable String line) {
-    if (line == null) {
-      return Collections.emptyList();
-    }
-    return newArrayList(Splitter.on(";").omitEmptyStrings().split(line));
-  }
-
-  private String[] splitColumn(String column) {
-    return column.split("=");
-  }
-
   private boolean isSameCommit(String date, String previousDate, String author, String previousAuthor) {
     return author.equals(previousAuthor) && date.equals(previousDate);
   }
index 05b00a713346782851a7249821e2c9477ef52910..bf90536acd96b900eb7a25f7fa2f76407a266179 100644 (file)
@@ -41,7 +41,7 @@ public class ShowAction implements RequestHandler {
 
   void define(WebService.NewController controller) {
     WebService.NewAction action = controller.createAction("show")
-      .setDescription("Get source code")
+      .setDescription("Get source code. Require Browse permission on file's project")
       .setSince("4.4")
       .setResponseExample(Resources.getResource(getClass(), "example-show.json"))
       .setHandler(this);
diff --git a/sonar-server/src/main/java/org/sonar/server/test/CoverageService.java b/sonar-server/src/main/java/org/sonar/server/test/CoverageService.java
new file mode 100644 (file)
index 0000000..872a145
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.test;
+
+import org.sonar.api.ServerComponent;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.measure.db.MeasureDataDao;
+import org.sonar.core.measure.db.MeasureDataDto;
+import org.sonar.server.user.UserSession;
+
+import javax.annotation.CheckForNull;
+
+public class CoverageService implements ServerComponent {
+
+  private final MeasureDataDao measureDataDao;
+
+  public CoverageService(MeasureDataDao measureDataDao) {
+    this.measureDataDao = measureDataDao;
+  }
+
+  public void checkPermission(String fileKey) {
+    UserSession.get().checkComponentPermission(UserRole.CODEVIEWER, fileKey);
+  }
+
+  /**
+   * Warning - does not check permission
+   */
+  @CheckForNull
+  public String getHitsData(String fileKey) {
+    return findDataFromComponent(fileKey, CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY);
+  }
+
+  /**
+   * Warning - does not check permission
+   */
+  @CheckForNull
+  public String getConditionsData(String fileKey) {
+    return findDataFromComponent(fileKey, CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY);
+  }
+
+  /**
+   * Warning - does not check permission
+   */
+  @CheckForNull
+  public String getCoveredConditionsData(String fileKey) {
+    return findDataFromComponent(fileKey, CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY);
+  }
+
+  @CheckForNull
+  private String findDataFromComponent(String fileKey, String metricKey) {
+    MeasureDataDto data = measureDataDao.findByComponentKeyAndMetricKey(fileKey, metricKey);
+    if (data != null) {
+      return data.getData();
+    }
+    return null;
+  }
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/test/ws/CoverageShowAction.java b/sonar-server/src/main/java/org/sonar/server/test/ws/CoverageShowAction.java
new file mode 100644 (file)
index 0000000..44897f9
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.test.ws;
+
+import com.google.common.io.Resources;
+import org.apache.commons.lang.ObjectUtils;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.RequestHandler;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.KeyValueFormat;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.server.test.CoverageService;
+
+import java.util.Map;
+
+public class CoverageShowAction implements RequestHandler {
+
+  private final CoverageService coverageService;
+
+  public CoverageShowAction(CoverageService coverageService) {
+    this.coverageService = coverageService;
+  }
+
+  void define(WebService.NewController controller) {
+    WebService.NewAction action = controller.createAction("show")
+      .setDescription("Get code coverage. Require Browse permission on file's project")
+      .setSince("4.4")
+      .setResponseExample(Resources.getResource(getClass(), "coverage-example-show.json"))
+      .setHandler(this);
+
+    action
+      .createParam("key")
+      .setRequired(true)
+      .setDescription("File key")
+      .setExampleValue("my_project:/src/foo/Bar.php");
+
+    action
+      .createParam("from")
+      .setDescription("First line to return. Starts at 1")
+      .setExampleValue("10")
+      .setDefaultValue("1");
+
+    action
+      .createParam("to")
+      .setDescription("Last line to return (inclusive)")
+      .setExampleValue("20");
+
+    action
+      .createParam("type")
+      .setDescription("Type of coverage info to return :" +
+        "<ul>" +
+        "<li>UT : Unit Tests</li>" +
+        "<li>IT : Integration Tests</li>" +
+        "<li>OVERALL : Unit and Integration Tests</li>" +
+        "</ul>")
+      .setPossibleValues("UT", "IT", "OVERALL");
+  }
+
+  @Override
+  public void handle(Request request, Response response) {
+    String fileKey = request.mandatoryParam("key");
+    coverageService.checkPermission(fileKey);
+
+    int from = Math.max(request.mandatoryParamAsInt("from"), 1);
+    int to = (Integer) ObjectUtils.defaultIfNull(request.paramAsInt("to"), Integer.MAX_VALUE);
+
+    JsonWriter json = response.newJsonWriter().beginObject();
+
+    String hits = coverageService.getHitsData(fileKey);
+    if (hits != null) {
+      Map<Integer, Integer> hitsByLine =  KeyValueFormat.parseIntInt(hits);
+    writeCoverage(hitsByLine, from, to, json);
+    }
+
+    json.endObject().close();
+  }
+
+  private void writeCoverage(Map<Integer, Integer> hitsByLine, int from, int to, JsonWriter json) {
+    json.name("coverage").beginArray();
+    for (Map.Entry<Integer, Integer> entry : hitsByLine.entrySet()) {
+      Integer line = entry.getKey();
+      if (line >= from && line <= to) {
+        Integer hits = entry.getValue();
+        json.beginArray();
+        json.value(line);
+        json.value(hits);
+        json.endArray();
+      }
+    }
+    json.endArray();
+  }
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/test/ws/CoverageWs.java b/sonar-server/src/main/java/org/sonar/server/test/ws/CoverageWs.java
new file mode 100644 (file)
index 0000000..a85f07d
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.test.ws;
+
+import org.sonar.api.server.ws.WebService;
+
+public class CoverageWs implements WebService {
+
+  private final CoverageShowAction showAction;
+
+  public CoverageWs(CoverageShowAction showAction) {
+    this.showAction = showAction;
+  }
+
+  @Override
+  public void define(Context context) {
+    NewController controller = context.createController("api/coverage")
+      .setSince("4.4")
+      .setDescription("Display coverage information");
+
+    showAction.define(controller);
+
+    controller.done();
+  }
+
+}
index 4f442b3ca06f234e40665b86ad14e7c8b848f617..c5b02ef3a5cd3d296a8c0e61e26917da5aab656e 100644 (file)
@@ -44,7 +44,7 @@ public class TestsWs implements WebService {
       .setSince("3.5")
       .setInternal(true)
       .setHandler(RailsHandler.INSTANCE)
-      .setResponseExample(Resources.getResource(this.getClass(), "example-plan.json"));
+      .setResponseExample(Resources.getResource(this.getClass(), "tests-example-plan.json"));
 
     action.createParam("resource")
       .setRequired(true)
@@ -59,7 +59,7 @@ public class TestsWs implements WebService {
       .setSince("3.5")
       .setInternal(true)
       .setHandler(RailsHandler.INSTANCE)
-      .setResponseExample(Resources.getResource(this.getClass(), "example-testable.json"));
+      .setResponseExample(Resources.getResource(this.getClass(), "tests-example-testable.json"));
 
     action.createParam("resource")
       .setRequired(true)
index fe08bf2eb403d92512ec58b51bef97eb605ef7a8..a7c4ee00a4e23660b6b98b3d6be5cb3d8b59f28e 100644 (file)
@@ -25,8 +25,11 @@ import com.google.common.collect.HashMultimap;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.core.resource.ResourceDao;
+import org.sonar.core.resource.ResourceDto;
 import org.sonar.core.user.AuthorizationDao;
 import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.exceptions.UnauthorizedException;
 import org.sonar.server.platform.Platform;
 
@@ -155,6 +158,17 @@ public class UserSession {
     return this;
   }
 
+  /**
+   * Ensures that user implies the specified project permission on a component. If not a {@link org.sonar.server.exceptions.ForbiddenException} is thrown.
+   */
+  public UserSession checkComponentPermission(String projectPermission, String componentKey) {
+    ResourceDto project = resourceDao().getRootProjectByComponentKey(componentKey);
+    if (project == null) {
+      throw new NotFoundException(String.format("Component '%s' does not exist", componentKey));
+    }
+    return checkProjectPermission(projectPermission, project.getKey());
+  }
+
   /**
    * Does the user have the given project permission ?
    */
@@ -173,6 +187,10 @@ public class UserSession {
     return Platform.component(AuthorizationDao.class);
   }
 
+  ResourceDao resourceDao() {
+    return Platform.component(ResourceDao.class);
+  }
+
   public static UserSession get() {
     return Objects.firstNonNull(THREAD_LOCAL.get(), ANONYMOUS);
   }
diff --git a/sonar-server/src/main/resources/org/sonar/server/test/ws/coverage-example-show.json b/sonar-server/src/main/resources/org/sonar/server/test/ws/coverage-example-show.json
new file mode 100644 (file)
index 0000000..84565ed
--- /dev/null
@@ -0,0 +1,33 @@
+{
+  "coverage": [
+    [
+      36,
+      1
+    ],
+    [
+      37,
+      1
+    ],
+    [
+      38,
+      1
+    ],
+    [
+      39,
+      1
+    ],
+    [
+      43,
+      1
+    ],
+    [
+      48,
+      1
+    ],
+    [
+      53,
+      1
+    ]
+  ]
+
+}
diff --git a/sonar-server/src/main/resources/org/sonar/server/test/ws/example-plan.json b/sonar-server/src/main/resources/org/sonar/server/test/ws/example-plan.json
deleted file mode 100644 (file)
index 5cc43b7..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-{
-  "key": "org.codehaus.sonar.plugins:sonar-cpd-plugin:src/test/java/org/sonar/plugins/cpd/SonarBridgeEngineTest.java",
-  "name": "SonarBridgeEngineTest.java",
-  "longName": "src/test/java/org/sonar/plugins/cpd/SonarBridgeEngineTest.java",
-  "testCases": [
-    {
-      "name": "shouldReturnDefaultBlockSize",
-      "type": "UNIT",
-      "durationInMs": 2,
-      "status": "OK",
-      "coverages": [
-        {
-          "key": "org.codehaus.sonar.plugins:sonar-cpd-plugin:src/main/java/org/sonar/plugins/cpd/SonarBridgeEngine.java",
-          "name": "SonarBridgeEngine.java",
-          "longName": "src/main/java/org/sonar/plugins/cpd/SonarBridgeEngine.java",
-          "lines": [
-            68,
-            69,
-            70,
-            71,
-            72,
-            73,
-            155,
-            156,
-            157,
-            158,
-            160
-          ]
-        },
-        {
-          "key": "org.codehaus.sonar.plugins:sonar-cpd-plugin:src/main/java/org/sonar/plugins/cpd/CpdEngine.java",
-          "name": "CpdEngine.java",
-          "longName": "src/main/java/org/sonar/plugins/cpd/CpdEngine.java",
-          "lines": [
-            27
-          ]
-        }
-      ]
-    }
-  ]
-}
diff --git a/sonar-server/src/main/resources/org/sonar/server/test/ws/example-testable.json b/sonar-server/src/main/resources/org/sonar/server/test/ws/example-testable.json
deleted file mode 100644 (file)
index 902aaa4..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-{
-  "key": "org.codehaus.sonar.plugins:sonar-cpd-plugin:src/main/java/org/sonar/plugins/cpd/SonarBridgeEngine.java",
-  "name": "SonarBridgeEngine.java",
-  "longName": "src/main/java/org/sonar/plugins/cpd/SonarBridgeEngine.java",
-  "coveredLines": [
-    56,
-    68,
-    69,
-    70,
-    71,
-    72,
-    73,
-    76,
-    77,
-    146,
-    147,
-    148,
-    150,
-    155,
-    156,
-    157,
-    158,
-    160,
-    166,
-    167,
-    168,
-    170,
-    171,
-    174
-  ],
-  "coverages": [
-    {
-      "lines": [
-        68,
-        69,
-        70,
-        71,
-        72,
-        73,
-        76,
-        77
-      ],
-      "name": "test_engine",
-      "status": "OK",
-      "durationInMs": 2,
-      "testPlan": "org.codehaus.sonar.plugins:sonar-cpd-plugin:src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java"
-    }
-  ]
-}
diff --git a/sonar-server/src/main/resources/org/sonar/server/test/ws/tests-example-plan.json b/sonar-server/src/main/resources/org/sonar/server/test/ws/tests-example-plan.json
new file mode 100644 (file)
index 0000000..5cc43b7
--- /dev/null
@@ -0,0 +1,41 @@
+{
+  "key": "org.codehaus.sonar.plugins:sonar-cpd-plugin:src/test/java/org/sonar/plugins/cpd/SonarBridgeEngineTest.java",
+  "name": "SonarBridgeEngineTest.java",
+  "longName": "src/test/java/org/sonar/plugins/cpd/SonarBridgeEngineTest.java",
+  "testCases": [
+    {
+      "name": "shouldReturnDefaultBlockSize",
+      "type": "UNIT",
+      "durationInMs": 2,
+      "status": "OK",
+      "coverages": [
+        {
+          "key": "org.codehaus.sonar.plugins:sonar-cpd-plugin:src/main/java/org/sonar/plugins/cpd/SonarBridgeEngine.java",
+          "name": "SonarBridgeEngine.java",
+          "longName": "src/main/java/org/sonar/plugins/cpd/SonarBridgeEngine.java",
+          "lines": [
+            68,
+            69,
+            70,
+            71,
+            72,
+            73,
+            155,
+            156,
+            157,
+            158,
+            160
+          ]
+        },
+        {
+          "key": "org.codehaus.sonar.plugins:sonar-cpd-plugin:src/main/java/org/sonar/plugins/cpd/CpdEngine.java",
+          "name": "CpdEngine.java",
+          "longName": "src/main/java/org/sonar/plugins/cpd/CpdEngine.java",
+          "lines": [
+            27
+          ]
+        }
+      ]
+    }
+  ]
+}
diff --git a/sonar-server/src/main/resources/org/sonar/server/test/ws/tests-example-testable.json b/sonar-server/src/main/resources/org/sonar/server/test/ws/tests-example-testable.json
new file mode 100644 (file)
index 0000000..902aaa4
--- /dev/null
@@ -0,0 +1,49 @@
+{
+  "key": "org.codehaus.sonar.plugins:sonar-cpd-plugin:src/main/java/org/sonar/plugins/cpd/SonarBridgeEngine.java",
+  "name": "SonarBridgeEngine.java",
+  "longName": "src/main/java/org/sonar/plugins/cpd/SonarBridgeEngine.java",
+  "coveredLines": [
+    56,
+    68,
+    69,
+    70,
+    71,
+    72,
+    73,
+    76,
+    77,
+    146,
+    147,
+    148,
+    150,
+    155,
+    156,
+    157,
+    158,
+    160,
+    166,
+    167,
+    168,
+    170,
+    171,
+    174
+  ],
+  "coverages": [
+    {
+      "lines": [
+        68,
+        69,
+        70,
+        71,
+        72,
+        73,
+        76,
+        77
+      ],
+      "name": "test_engine",
+      "status": "OK",
+      "durationInMs": 2,
+      "testPlan": "org.codehaus.sonar.plugins:sonar-cpd-plugin:src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java"
+    }
+  ]
+}
index e21d3e4399072fe0e1330df41927c652e396be12..88d5ed5e200086428396be3ade6b98adf06ecc99 100644 (file)
@@ -28,8 +28,6 @@ import org.mockito.runners.MockitoJUnitRunner;
 import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.web.UserRole;
 import org.sonar.core.measure.db.MeasureDataDao;
-import org.sonar.core.resource.ResourceDao;
-import org.sonar.core.resource.ResourceDto;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.user.MockUserSession;
 
@@ -46,10 +44,7 @@ public class SourceServiceTest {
   HtmlSourceDecorator sourceDecorator;
 
   @Mock
-  DeprecatedSourceDecorator deprecatedSourceDecorator;;
-
-  @Mock
-  ResourceDao resourceDao;
+  DeprecatedSourceDecorator deprecatedSourceDecorator;
 
   @Mock
   MeasureDataDao measureDataDao;
@@ -58,15 +53,14 @@ public class SourceServiceTest {
 
   @Before
   public void setUp() throws Exception {
-    service = new SourceService(sourceDecorator, deprecatedSourceDecorator, resourceDao, measureDataDao);
+    service = new SourceService(sourceDecorator, deprecatedSourceDecorator, measureDataDao);
   }
 
   @Test
   public void get_lines() throws Exception {
     String projectKey = "org.sonar.sample";
     String componentKey = "org.sonar.sample:Sample";
-    MockUserSession.set().addProjectPermissions(UserRole.CODEVIEWER, projectKey);
-    when(resourceDao.getRootProjectByComponentKey(componentKey)).thenReturn(new ResourceDto().setKey(projectKey));
+    MockUserSession.set().addProjectPermissions(UserRole.CODEVIEWER, projectKey).addComponent(componentKey, projectKey);
 
     service.getLinesAsHtml(componentKey);
 
@@ -78,7 +72,6 @@ public class SourceServiceTest {
     String projectKey = "org.sonar.sample";
     String componentKey = "org.sonar.sample:Sample";
     MockUserSession.set().addProjectPermissions(UserRole.CODEVIEWER, projectKey);
-    when(resourceDao.getRootProjectByComponentKey(componentKey)).thenReturn(null);
 
     try {
       service.getLinesAsHtml(componentKey);
@@ -94,8 +87,7 @@ public class SourceServiceTest {
   public void get_block_of_lines() throws Exception {
     String projectKey = "org.sonar.sample";
     String componentKey = "org.sonar.sample:Sample";
-    MockUserSession.set().addProjectPermissions(UserRole.CODEVIEWER, projectKey);
-    when(resourceDao.getRootProjectByComponentKey(componentKey)).thenReturn(new ResourceDto().setKey(projectKey));
+    MockUserSession.set().addProjectPermissions(UserRole.CODEVIEWER, projectKey).addComponent(componentKey, projectKey);;
 
     service.getLinesAsHtml(componentKey, 1, 2);
 
@@ -106,8 +98,7 @@ public class SourceServiceTest {
   public void get_lines_from_deprecated_source_decorator_when_no_data_from_new_decorator() throws Exception {
     String projectKey = "org.sonar.sample";
     String componentKey = "org.sonar.sample:Sample";
-    MockUserSession.set().addProjectPermissions(UserRole.CODEVIEWER, projectKey);
-    when(resourceDao.getRootProjectByComponentKey(componentKey)).thenReturn(new ResourceDto().setKey(projectKey));
+    MockUserSession.set().addProjectPermissions(UserRole.CODEVIEWER, projectKey).addComponent(componentKey, projectKey);;
     when(sourceDecorator.getDecoratedSourceAsHtml(eq(componentKey), anyInt(), anyInt())).thenReturn(Collections.<String>emptyList());
 
     service.getLinesAsHtml(componentKey, 1, 2);
diff --git a/sonar-server/src/test/java/org/sonar/server/test/CoverageServiceTest.java b/sonar-server/src/test/java/org/sonar/server/test/CoverageServiceTest.java
new file mode 100644 (file)
index 0000000..dcd9641
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.test;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.measure.db.MeasureDataDao;
+import org.sonar.server.user.MockUserSession;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CoverageServiceTest {
+
+  @org.junit.Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  @Mock
+  MeasureDataDao measureDataDao;
+
+  static final String COMPONENT_KEY = "org.sonar.sample:Sample";
+
+  CoverageService service;
+
+  @Before
+  public void setUp() throws Exception {
+    service = new CoverageService(measureDataDao);
+  }
+
+  @Test
+  public void check_permission() throws Exception {
+    String projectKey = "org.sonar.sample";
+    MockUserSession.set().addProjectPermissions(UserRole.CODEVIEWER, projectKey);
+    MockUserSession.set().addProjectPermissions(UserRole.CODEVIEWER, projectKey).addComponent(COMPONENT_KEY, projectKey);
+
+    service.checkPermission(COMPONENT_KEY);
+  }
+
+  @Test
+  public void get_hits_data() throws Exception {
+    service.getHitsData(COMPONENT_KEY);
+    verify(measureDataDao).findByComponentKeyAndMetricKey(COMPONENT_KEY, CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY);
+  }
+
+  @Test
+  public void not_get_hits_data_if_no_data() throws Exception {
+    when(measureDataDao.findByComponentKeyAndMetricKey(eq(COMPONENT_KEY), anyString())).thenReturn(null);
+    assertThat(service.getHitsData(COMPONENT_KEY)).isNull();
+  }
+
+  @Test
+  public void get_conditions_data() throws Exception {
+    service.getConditionsData(COMPONENT_KEY);
+    verify(measureDataDao).findByComponentKeyAndMetricKey(COMPONENT_KEY, CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY);
+  }
+
+  @Test
+  public void get_coverered_conditions_data() throws Exception {
+    service.getCoveredConditionsData(COMPONENT_KEY);
+    verify(measureDataDao).findByComponentKeyAndMetricKey(COMPONENT_KEY, CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY);
+  }
+
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/test/ws/CoverageShowActionTest.java b/sonar-server/src/test/java/org/sonar/server/test/ws/CoverageShowActionTest.java
new file mode 100644 (file)
index 0000000..f49d257
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.test.ws;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.server.test.CoverageService;
+import org.sonar.server.ws.WsTester;
+
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CoverageShowActionTest {
+
+  @Mock
+  CoverageService coverageService;
+
+  WsTester tester;
+
+  @Before
+  public void setUp() throws Exception {
+    tester = new WsTester(new CoverageWs(new CoverageShowAction(coverageService)));
+  }
+
+  @Test
+  public void show_coverage() throws Exception {
+    String fileKey = "src/Foo.java";
+    when(coverageService.getHitsData(fileKey)).thenReturn("1=8;2=2;3=5;4=1;5=1");
+
+    WsTester.TestRequest request = tester.newGetRequest("api/coverage", "show").setParam("key", fileKey);
+
+    request.execute().assertJson(getClass(), "show_coverage.json");
+  }
+
+  @Test
+  public void show_coverage_with_from_and_to() throws Exception {
+    String fileKey = "src/Foo.java";
+    when(coverageService.getHitsData(fileKey)).thenReturn("1=8;2=2;3=5;4=1;5=1");
+
+    WsTester.TestRequest request = tester.newGetRequest("api/coverage", "show").setParam("key", fileKey).setParam("from", "3").setParam("to", "4");
+
+    request.execute().assertJson(getClass(), "show_coverage_with_from_and_to.json");
+  }
+
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/test/ws/CoverageWsTest.java b/sonar-server/src/test/java/org/sonar/server/test/ws/CoverageWsTest.java
new file mode 100644 (file)
index 0000000..aa1ad9e
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.test.ws;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.server.test.CoverageService;
+import org.sonar.server.ws.WsTester;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class CoverageWsTest {
+
+  WebService.Controller controller;
+
+  @Before
+  public void setUp() throws Exception {
+    WsTester tester = new WsTester(new CoverageWs(new CoverageShowAction(mock(CoverageService.class))));
+    controller = tester.controller("api/coverage");
+  }
+
+  @Test
+  public void define_controller() throws Exception {
+    assertThat(controller).isNotNull();
+    assertThat(controller.description()).isNotEmpty();
+    assertThat(controller.since()).isEqualTo("4.4");
+    assertThat(controller.isInternal()).isFalse();
+    assertThat(controller.actions()).hasSize(1);
+  }
+
+  @Test
+  public void define_show_action() throws Exception {
+    WebService.Action action = controller.action("show");
+    assertThat(action).isNotNull();
+    assertThat(action.isPost()).isFalse();
+    assertThat(action.handler()).isNotNull();
+    assertThat(action.responseExampleAsString()).isNotEmpty();
+    assertThat(action.params()).hasSize(4);
+  }
+
+}
index 4f35726044a941632d2677a354c0555833b279be..aa39fe5f4e566833d372716db17bb084ab678e90 100644 (file)
@@ -20,6 +20,8 @@
 package org.sonar.server.user;
 
 import com.google.common.collect.HashMultimap;
+import org.sonar.core.resource.ResourceDao;
+import org.sonar.core.resource.ResourceDto;
 import org.sonar.core.user.AuthorizationDao;
 
 import javax.annotation.Nullable;
@@ -30,12 +32,19 @@ import java.util.Locale;
 
 import static com.google.common.collect.Lists.newArrayList;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 public class MockUserSession extends UserSession {
 
+  private final AuthorizationDao authorizationDao;
+
+  private final ResourceDao resourceDao;
+
   private MockUserSession() {
     globalPermissions = Collections.emptyList();
     projectKeyByPermission = HashMultimap.create();
+    authorizationDao = mock(AuthorizationDao.class);
+    resourceDao = mock(ResourceDao.class);
   }
 
   public static MockUserSession set() {
@@ -79,8 +88,18 @@ public class MockUserSession extends UserSession {
     return this;
   }
 
+  public MockUserSession addComponent(String componentKey, String projectKey) {
+    when(resourceDao.getRootProjectByComponentKey(componentKey)).thenReturn(new ResourceDto().setKey(projectKey));
+    return this;
+  }
+
   @Override
   AuthorizationDao authorizationDao() {
-    return mock(AuthorizationDao.class);
+    return authorizationDao;
+  }
+
+  @Override
+  ResourceDao resourceDao() {
+    return resourceDao;
   }
 }
index b4b02d35060810ee5f2c5f659af074f4a7345474..f33d1da8fcb772161badb22d092a66f17fda25c9 100644 (file)
 package org.sonar.server.user;
 
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.sonar.api.web.UserRole;
 import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.core.resource.ResourceDao;
+import org.sonar.core.resource.ResourceDto;
 import org.sonar.core.user.AuthorizationDao;
 import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+
+import javax.annotation.Nullable;
 
 import java.util.Arrays;
 import java.util.Locale;
@@ -34,6 +40,10 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 public class UserSessionTest {
+
+  @org.junit.Rule
+  public ExpectedException thrown = ExpectedException.none();
+
   @Test
   public void getSession_get_anonymous_by_default() throws Exception {
     UserSession.remove();
@@ -128,11 +138,55 @@ public class UserSessionTest {
     session.checkProjectPermission(UserRole.USER, "com.foo:Bar");
   }
 
+  @Test
+  public void check_component_permission_ok() throws Exception {
+    AuthorizationDao authorizationDao = mock(AuthorizationDao.class);
+    ResourceDao resourceDao = mock(ResourceDao.class);
+    UserSession session = new SpyUserSession("marius", authorizationDao, resourceDao).setUserId(1);
+
+    when(resourceDao.getRootProjectByComponentKey("com.foo:Bar:BarFile.xoo")).thenReturn(new ResourceDto().setKey("com.foo:Bar"));
+    when(authorizationDao.selectAuthorizedRootProjectsKeys(1, UserRole.USER)).thenReturn(newArrayList("com.foo:Bar"));
+
+    session.checkComponentPermission(UserRole.USER, "com.foo:Bar:BarFile.xoo");
+  }
+
+  @Test(expected = ForbiddenException.class)
+  public void check_component_permission_ko() throws Exception {
+    AuthorizationDao authorizationDao = mock(AuthorizationDao.class);
+    ResourceDao resourceDao = mock(ResourceDao.class);
+    UserSession session = new SpyUserSession("marius", authorizationDao, resourceDao).setUserId(1);
+
+    when(resourceDao.getRootProjectByComponentKey("com.foo:Bar:BarFile.xoo")).thenReturn(new ResourceDto().setKey("com.foo:Bar2"));
+    when(authorizationDao.selectAuthorizedRootProjectsKeys(1, UserRole.USER)).thenReturn(newArrayList("com.foo:Bar"));
+
+    session.checkComponentPermission(UserRole.USER, "com.foo:Bar:BarFile.xoo");
+  }
+
+  @Test
+  public void check_component_permission_when_project_not_found() throws Exception {
+    thrown.expect(NotFoundException.class);
+    thrown.expectMessage("Component 'com.foo:Bar:BarFile.xoo' does not exist");
+
+    AuthorizationDao authorizationDao = mock(AuthorizationDao.class);
+    ResourceDao resourceDao = mock(ResourceDao.class);
+    UserSession session = new SpyUserSession("marius", authorizationDao, resourceDao).setUserId(1);
+
+    when(resourceDao.getRootProjectByComponentKey("com.foo:Bar:BarFile.xoo")).thenReturn(null);
+
+    session.checkComponentPermission(UserRole.USER, "com.foo:Bar:BarFile.xoo");
+  }
+
   static class SpyUserSession extends UserSession {
     private AuthorizationDao authorizationDao;
+    private ResourceDao resourceDao;
 
     SpyUserSession(String login, AuthorizationDao authorizationDao) {
+      this(login, authorizationDao, null);
+    }
+
+    SpyUserSession(String login, AuthorizationDao authorizationDao, @Nullable ResourceDao resourceDao) {
       this.authorizationDao = authorizationDao;
+      this.resourceDao = resourceDao;
       setLogin(login);
     }
 
@@ -140,5 +194,11 @@ public class UserSessionTest {
     AuthorizationDao authorizationDao() {
       return authorizationDao;
     }
+
+    @Override
+    ResourceDao resourceDao() {
+      return resourceDao;
+    }
+
   }
 }
diff --git a/sonar-server/src/test/resources/org/sonar/server/test/ws/CoverageShowActionTest/show_coverage.json b/sonar-server/src/test/resources/org/sonar/server/test/ws/CoverageShowActionTest/show_coverage.json
new file mode 100644 (file)
index 0000000..77d58b6
--- /dev/null
@@ -0,0 +1,9 @@
+{
+  "coverage": [
+    [1, 8],
+    [2, 2],
+    [3, 5],
+    [4, 1],
+    [5, 1]
+  ]
+}
diff --git a/sonar-server/src/test/resources/org/sonar/server/test/ws/CoverageShowActionTest/show_coverage_with_from_and_to.json b/sonar-server/src/test/resources/org/sonar/server/test/ws/CoverageShowActionTest/show_coverage_with_from_and_to.json
new file mode 100644 (file)
index 0000000..c6a0946
--- /dev/null
@@ -0,0 +1,6 @@
+{
+  "coverage": [
+    [3, 5],
+    [4, 1]
+  ]
+}