From 25e95eda0c4c00bf5a92543db8b5e990f901d117 Mon Sep 17 00:00:00 2001 From: Teryk Bellahsene Date: Tue, 20 Jun 2017 11:41:57 +0200 Subject: [PATCH] SONAR-9441 WS api/duplications/show fails properly when no parameter provided --- .../java/it/duplication/DuplicationsTest.java | 74 +++++++---- .../duplication/ws/DuplicationsWsAction.java | 27 ++++ .../server/duplication/ws/ShowAction.java | 39 +++--- ...onWriter.java => ShowResponseBuilder.java} | 120 +++++++++--------- .../platformlevel/PlatformLevel4.java | 4 +- .../{example-show.json => show-example.json} | 28 +++- .../duplication/ws/DuplicationsWsTest.java | 2 +- .../server/duplication/ws/ShowActionTest.java | 49 ++++--- ...Test.java => ShowResponseBuilderTest.java} | 71 +++++------ .../src/main/protobuf/ws-duplications.proto | 54 ++++++++ 10 files changed, 294 insertions(+), 174 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsWsAction.java rename server/sonar-server/src/main/java/org/sonar/server/duplication/ws/{DuplicationsJsonWriter.java => ShowResponseBuilder.java} (58%) rename server/sonar-server/src/main/resources/org/sonar/server/duplication/ws/{example-show.json => show-example.json} (66%) rename server/sonar-server/src/test/java/org/sonar/server/duplication/ws/{DuplicationsJsonWriterTest.java => ShowResponseBuilderTest.java} (76%) create mode 100644 sonar-ws/src/main/protobuf/ws-duplications.proto diff --git a/it/it-tests/src/test/java/it/duplication/DuplicationsTest.java b/it/it-tests/src/test/java/it/duplication/DuplicationsTest.java index ea55e0eb24b..6eddcee5915 100644 --- a/it/it-tests/src/test/java/it/duplication/DuplicationsTest.java +++ b/it/it-tests/src/test/java/it/duplication/DuplicationsTest.java @@ -28,14 +28,19 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.WsResponse; import org.sonarqube.ws.client.issue.SearchWsRequest; import util.ItUtils; import util.issue.IssueRule; +import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; import static util.ItUtils.getMeasuresAsDoubleByMetricKey; +import static util.ItUtils.newAdminWsClient; import static util.ItUtils.runProjectAnalysis; import static util.ItUtils.setServerProperty; @@ -51,6 +56,8 @@ public class DuplicationsTest { @ClassRule public static final IssueRule issueRule = IssueRule.from(orchestrator); + private static WsClient adminWsClient; + @BeforeClass public static void analyzeProjects() { orchestrator.resetData(); @@ -62,6 +69,8 @@ public class DuplicationsTest { // Set minimum tokens to a big value in order to not get duplications setServerProperty(orchestrator, "sonar.cpd.xoo.minimumTokens", "1000"); analyzeProject(WITHOUT_ENOUGH_TOKENS); + + adminWsClient = newAdminWsClient(orchestrator); } @AfterClass @@ -69,6 +78,37 @@ public class DuplicationsTest { setServerProperty(orchestrator, "sonar.cpd.xoo.minimumTokens", null); } + private static Map getMeasures(String key) { + return getMeasuresAsDoubleByMetricKey(orchestrator, key, "duplicated_lines", "duplicated_blocks", "duplicated_files", "duplicated_lines_density"); + } + + private static void verifyDuplicationMeasures(String componentKey, int duplicatedBlocks, int duplicatedLines, int duplicatedFiles, double duplicatedLinesDensity) { + Map measures = getMeasures(componentKey); + assertThat(measures.get("duplicated_blocks").intValue()).isEqualTo(duplicatedBlocks); + assertThat(measures.get("duplicated_lines").intValue()).isEqualTo(duplicatedLines); + assertThat(measures.get("duplicated_files").intValue()).isEqualTo(duplicatedFiles); + assertThat(measures.get("duplicated_lines_density")).isEqualTo(duplicatedLinesDensity); + } + + private static void analyzeProject(String projectKey, String... additionalProperties) { + orchestrator.getServer().provisionProject(projectKey, projectKey); + orchestrator.getServer().associateProjectToQualityProfile(projectKey, "xoo", "xoo-duplication-profile"); + + runProjectAnalysis(orchestrator, "duplications/file-duplications", + ObjectArrays.concat( + new String[] { + "sonar.projectKey", projectKey, + "sonar.projectName", projectKey + }, + additionalProperties, String.class)); + } + + private static void verifyWsResultOnDuplicateFile(String fileKey, String ws, String expectedFilePath) throws Exception { + String duplication = orchestrator.getServer().adminWsClient().get(ws, "key", fileKey); + assertEquals(IOUtils.toString(CrossProjectDuplicationsTest.class.getResourceAsStream("/duplication/DuplicationsTest/" + expectedFilePath), "UTF-8"), duplication, + false); + } + @Test public void duplicated_lines_within_same_file() { verifyDuplicationMeasures(DUPLICATIONS + ":src/main/xoo/duplicated_lines_within_same_file/DuplicatedLinesInSameFile.xoo", @@ -137,35 +177,13 @@ public class DuplicationsTest { "api/duplications/show", "duplications_show-expected.json"); } - private static Map getMeasures(String key) { - return getMeasuresAsDoubleByMetricKey(orchestrator, key, "duplicated_lines", "duplicated_blocks", "duplicated_files", "duplicated_lines_density"); - } - - private static void verifyDuplicationMeasures(String componentKey, int duplicatedBlocks, int duplicatedLines, int duplicatedFiles, double duplicatedLinesDensity) { - Map measures = getMeasures(componentKey); - assertThat(measures.get("duplicated_blocks").intValue()).isEqualTo(duplicatedBlocks); - assertThat(measures.get("duplicated_lines").intValue()).isEqualTo(duplicatedLines); - assertThat(measures.get("duplicated_files").intValue()).isEqualTo(duplicatedFiles); - assertThat(measures.get("duplicated_lines_density")).isEqualTo(duplicatedLinesDensity); - } - - private static void analyzeProject(String projectKey, String... additionalProperties) { - orchestrator.getServer().provisionProject(projectKey, projectKey); - orchestrator.getServer().associateProjectToQualityProfile(projectKey, "xoo", "xoo-duplication-profile"); - - runProjectAnalysis(orchestrator, "duplications/file-duplications", - ObjectArrays.concat( - new String[] { - "sonar.projectKey", projectKey, - "sonar.projectName", projectKey - }, - additionalProperties, String.class)); - } + // SONAR-9441 + @Test + public void fail_properly_when_no_parameter() { + WsResponse result = adminWsClient.wsConnector().call(new GetRequest("api/duplications/show")); - private static void verifyWsResultOnDuplicateFile(String fileKey, String ws, String expectedFilePath) throws Exception { - String duplication = orchestrator.getServer().adminWsClient().get(ws, "key", fileKey); - assertEquals(IOUtils.toString(CrossProjectDuplicationsTest.class.getResourceAsStream("/duplication/DuplicationsTest/" + expectedFilePath), "UTF-8"), duplication, - false); + assertThat(result.code()).isEqualTo(HTTP_BAD_REQUEST); + assertThat(result.content()).contains("Either 'uuid' or 'key' must be provided, not both"); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsWsAction.java b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsWsAction.java new file mode 100644 index 00000000000..8c41d92d71b --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsWsAction.java @@ -0,0 +1,27 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.duplication.ws; + +import org.sonar.server.ws.WsAction; + +public interface DuplicationsWsAction extends WsAction { + // marker interface +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowAction.java b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowAction.java index 7e18c3df19e..31e5fa771a0 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowAction.java @@ -19,16 +19,13 @@ */ package org.sonar.server.duplication.ws; -import com.google.common.io.Resources; import java.util.List; -import java.util.Optional; import javax.annotation.CheckForNull; import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.server.ws.Change; 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.text.JsonWriter; import org.sonar.api.web.UserRole; import org.sonar.db.DbClient; import org.sonar.db.DbSession; @@ -39,30 +36,34 @@ import org.sonar.server.component.ComponentFinder; import org.sonar.server.user.UserSession; import static org.sonar.server.component.ComponentFinder.ParamNames.UUID_AND_KEY; +import static org.sonar.server.ws.WsUtils.writeProtobuf; -public class ShowAction implements RequestHandler { +public class ShowAction implements DuplicationsWsAction { private final DbClient dbClient; private final DuplicationsParser parser; - private final DuplicationsJsonWriter duplicationsJsonWriter; + private final ShowResponseBuilder responseBuilder; private final UserSession userSession; private final ComponentFinder componentFinder; - public ShowAction(DbClient dbClient, DuplicationsParser parser, - DuplicationsJsonWriter duplicationsJsonWriter, UserSession userSession, ComponentFinder componentFinder) { + public ShowAction(DbClient dbClient, DuplicationsParser parser, ShowResponseBuilder responseBuilder, UserSession userSession, ComponentFinder componentFinder) { this.dbClient = dbClient; this.parser = parser; - this.duplicationsJsonWriter = duplicationsJsonWriter; + this.responseBuilder = responseBuilder; this.userSession = userSession; this.componentFinder = componentFinder; } - void define(WebService.NewController controller) { + @Override + public void define(WebService.NewController controller) { WebService.NewAction action = controller.createAction("show") .setDescription("Get duplications. Require Browse permission on file's project") .setSince("4.4") .setHandler(this) - .setResponseExample(Resources.getResource(this.getClass(), "example-show.json")); + .setResponseExample(getClass().getResource("show-example.json")); + + action.setChangelog( + new Change("6.5", "The fields 'uuid', 'projectUuid', 'subProjectUuid' are deprecated in the response.")); action .createParam("key") @@ -71,21 +72,20 @@ public class ShowAction implements RequestHandler { action .createParam("uuid") - .setDescription("File UUID") + .setDeprecatedSince("6.5") + .setDescription("File ID. If provided, 'key' must not be provided.") .setExampleValue("584a89f2-8037-4f7b-b82c-8b45d2d63fb2"); } @Override public void handle(Request request, Response response) { - try (DbSession dbSession = dbClient.openSession(false); - JsonWriter json = response.newJsonWriter()) { + try (DbSession dbSession = dbClient.openSession(false)) { ComponentDto component = componentFinder.getByUuidOrKey(dbSession, request.param("uuid"), request.param("key"), UUID_AND_KEY); userSession.checkComponentPermission(UserRole.CODEVIEWER, component); - json.beginObject(); String duplications = findDataFromComponent(dbSession, component); List blocks = parser.parse(component, duplications, dbSession); - duplicationsJsonWriter.write(blocks, json, dbSession); - json.endObject(); + + writeProtobuf(responseBuilder.build(blocks, dbSession), request, response); } } @@ -95,7 +95,8 @@ public class ShowAction implements RequestHandler { .setComponentUuid(component.uuid()) .setMetricKey(CoreMetrics.DUPLICATIONS_DATA_KEY) .build(); - Optional measure = dbClient.measureDao().selectSingle(dbSession, query); - return measure.isPresent() ? measure.get().getData() : null; + return dbClient.measureDao().selectSingle(dbSession, query) + .map(MeasureDto::getData) + .orElse(null); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsJsonWriter.java b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowResponseBuilder.java similarity index 58% rename from server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsJsonWriter.java rename to server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowResponseBuilder.java index 4dc6513260e..9e61f607405 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsJsonWriter.java +++ b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowResponseBuilder.java @@ -24,46 +24,52 @@ import com.google.common.base.Optional; import java.util.List; import java.util.Map; import javax.annotation.Nullable; -import org.sonar.api.server.ServerSide; -import org.sonar.api.utils.text.JsonWriter; +import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.component.ComponentDao; import org.sonar.db.component.ComponentDto; +import org.sonarqube.ws.WsDuplications; +import org.sonarqube.ws.WsDuplications.Block; +import org.sonarqube.ws.WsDuplications.ShowResponse; import static com.google.common.collect.Maps.newHashMap; +import static org.sonar.core.util.Protobuf.setNullable; -@ServerSide -public class DuplicationsJsonWriter { +public class ShowResponseBuilder { private final ComponentDao componentDao; - public DuplicationsJsonWriter(ComponentDao componentDao) { - this.componentDao = componentDao; + public ShowResponseBuilder(DbClient dbClient) { + this.componentDao = dbClient.componentDao(); } @VisibleForTesting - void write(List blocks, JsonWriter json, DbSession session) { + ShowResponseBuilder(ComponentDao componentDao) { + this.componentDao = componentDao; + } + + ShowResponse build(List blocks, DbSession session) { + ShowResponse.Builder response = ShowResponse.newBuilder(); Map refByComponentKey = newHashMap(); - json.name("duplications").beginArray(); - writeDuplications(blocks, refByComponentKey, json); - json.endArray(); + blocks.stream() + .map(block -> toWsDuplication(block, refByComponentKey)) + .forEach(response::addDuplications); + + writeFiles(response, refByComponentKey, session); - json.name("files").beginObject(); - writeFiles(refByComponentKey, json, session); - json.endObject(); + return response.build(); } - private static void writeDuplications(List blocks, Map refByComponentKey, JsonWriter json) { - for (DuplicationsParser.Block block : blocks) { - json.beginObject().name("blocks").beginArray(); - for (DuplicationsParser.Duplication duplication : block.getDuplications()) { - writeDuplication(refByComponentKey, duplication, json); - } - json.endArray().endObject(); - } + private static WsDuplications.Duplication.Builder toWsDuplication(DuplicationsParser.Block block, Map refByComponentKey) { + WsDuplications.Duplication.Builder wsDuplication = WsDuplications.Duplication.newBuilder(); + block.getDuplications().stream() + .map(d -> toWsBlock(refByComponentKey, d)) + .forEach(wsDuplication::addBlocks); + + return wsDuplication; } - private static void writeDuplication(Map refByComponentKey, DuplicationsParser.Duplication duplication, JsonWriter json) { + private static Block.Builder toWsBlock(Map refByComponentKey, DuplicationsParser.Duplication duplication) { String ref = null; ComponentDto componentDto = duplication.file(); if (componentDto != null) { @@ -75,52 +81,52 @@ public class DuplicationsJsonWriter { } } - json.beginObject(); - json.prop("from", duplication.from()); - json.prop("size", duplication.size()); - json.prop("_ref", ref); - json.endObject(); + Block.Builder block = Block.newBuilder(); + block.setFrom(duplication.from()); + block.setSize(duplication.size()); + setNullable(ref, block::setRef); + + return block; } - private void writeFiles(Map refByComponentKey, JsonWriter json, DbSession session) { + private static WsDuplications.File toWsFile(ComponentDto file, @Nullable ComponentDto project, @Nullable ComponentDto subProject) { + WsDuplications.File.Builder wsFile = WsDuplications.File.newBuilder(); + wsFile.setKey(file.key()); + wsFile.setUuid(file.uuid()); + wsFile.setName(file.longName()); + + if (project != null) { + wsFile.setProject(project.key()); + wsFile.setProjectUuid(project.uuid()); + wsFile.setProjectName(project.longName()); + + // Do not return sub project if sub project and project are the same + boolean displaySubProject = subProject != null && !subProject.uuid().equals(project.uuid()); + if (displaySubProject) { + wsFile.setSubProject(subProject.key()); + wsFile.setSubProjectUuid(subProject.uuid()); + wsFile.setSubProjectName(subProject.longName()); + } + } + + return wsFile.build(); + } + + private void writeFiles(ShowResponse.Builder response, Map refByComponentKey, DbSession session) { Map projectsByUuid = newHashMap(); - Map parentProjectsByUuid = newHashMap(); + Map parentModulesByUuid = newHashMap(); + Map filesByRef = response.getMutableFiles(); + for (Map.Entry entry : refByComponentKey.entrySet()) { String componentKey = entry.getKey(); String ref = entry.getValue(); Optional fileOptional = componentDao.selectByKey(session, componentKey); if (fileOptional.isPresent()) { ComponentDto file = fileOptional.get(); - json.name(ref).beginObject(); - addFile(json, file); ComponentDto project = getProject(file.projectUuid(), projectsByUuid, session); - ComponentDto parentProject = getParentProject(file.getRootUuid(), parentProjectsByUuid, session); - addProject(json, project, parentProject); - - json.endObject(); - } - } - } - - private static void addFile(JsonWriter json, ComponentDto file) { - json.prop("key", file.key()); - json.prop("uuid", file.uuid()); - json.prop("name", file.longName()); - } - - private static void addProject(JsonWriter json, @Nullable ComponentDto project, @Nullable ComponentDto subProject) { - if (project != null) { - json.prop("project", project.key()); - json.prop("projectUuid", project.uuid()); - json.prop("projectName", project.longName()); - - // Do not return sub project if sub project and project are the same - boolean displaySubProject = subProject != null && !subProject.uuid().equals(project.uuid()); - if (displaySubProject) { - json.prop("subProject", subProject.key()); - json.prop("subProjectUuid", subProject.uuid()); - json.prop("subProjectName", subProject.longName()); + ComponentDto parentModule = getParentProject(file.getRootUuid(), parentModulesByUuid, session); + filesByRef.put(ref, toWsFile(file, project, parentModule)); } } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index 45722f00c6a..abf786f89c0 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -47,7 +47,7 @@ import org.sonar.server.component.ws.ComponentsWsModule; import org.sonar.server.debt.DebtModelPluginRepository; import org.sonar.server.debt.DebtModelXMLExporter; import org.sonar.server.debt.DebtRulesXMLImporter; -import org.sonar.server.duplication.ws.DuplicationsJsonWriter; +import org.sonar.server.duplication.ws.ShowResponseBuilder; import org.sonar.server.duplication.ws.DuplicationsParser; import org.sonar.server.duplication.ws.DuplicationsWs; import org.sonar.server.email.ws.EmailsWsModule; @@ -430,7 +430,7 @@ public class PlatformLevel4 extends PlatformLevel { // Duplications DuplicationsParser.class, DuplicationsWs.class, - DuplicationsJsonWriter.class, + ShowResponseBuilder.class, org.sonar.server.duplication.ws.ShowAction.class, // text diff --git a/server/sonar-server/src/main/resources/org/sonar/server/duplication/ws/example-show.json b/server/sonar-server/src/main/resources/org/sonar/server/duplication/ws/show-example.json similarity index 66% rename from server/sonar-server/src/main/resources/org/sonar/server/duplication/ws/example-show.json rename to server/sonar-server/src/main/resources/org/sonar/server/duplication/ws/show-example.json index 16be68f375b..64f3fb78903 100644 --- a/server/sonar-server/src/main/resources/org/sonar/server/duplication/ws/example-show.json +++ b/server/sonar-server/src/main/resources/org/sonar/server/duplication/ws/show-example.json @@ -3,33 +3,47 @@ { "blocks": [ { - "from": 94, "size": 101, "_ref": "1" + "from": 94, + "size": 101, + "_ref": "1" }, { - "from": 83, "size": 101, "_ref": "2" + "from": 83, + "size": 101, + "_ref": "2" } ] }, { "blocks": [ { - "from": 38, "size": 40, "_ref": "1" + "from": 38, + "size": 40, + "_ref": "1" }, { - "from": 29, "size": 39, "_ref": "2" + "from": 29, + "size": 39, + "_ref": "2" } ] }, { "blocks": [ { - "from": 148, "size": 24, "_ref": "1" + "from": 148, + "size": 24, + "_ref": "1" }, { - "from": 137, "size": 24, "_ref": "2" + "from": 137, + "size": 24, + "_ref": "2" }, { - "from": 137, "size": 24, "_ref": "3" + "from": 137, + "size": 24, + "_ref": "3" } ] } diff --git a/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/DuplicationsWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/DuplicationsWsTest.java index 6dff1f318f3..8cf850d2547 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/DuplicationsWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/DuplicationsWsTest.java @@ -35,7 +35,7 @@ public class DuplicationsWsTest { public UserSessionRule userSessionRule = UserSessionRule.standalone(); WsTester tester = new WsTester(new DuplicationsWs( - new ShowAction(mock(DbClient.class), mock(DuplicationsParser.class), mock(DuplicationsJsonWriter.class), userSessionRule, + new ShowAction(mock(DbClient.class), mock(DuplicationsParser.class), mock(ShowResponseBuilder.class), userSessionRule, mock(ComponentFinder.class)))); @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/ShowActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/ShowActionTest.java index 2300f6e3ff2..64774e7cc8f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/ShowActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/ShowActionTest.java @@ -35,43 +35,46 @@ import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.startup.RegisterMetrics; import org.sonar.server.tester.UserSessionRule; -import org.sonar.server.ws.WsTester; +import org.sonar.server.ws.TestRequest; +import org.sonar.server.ws.TestResponse; +import org.sonar.server.ws.WsActionTester; import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.db.component.SnapshotTesting.newAnalysis; import static org.sonar.db.measure.MeasureTesting.newMeasureDto; +import static org.sonar.test.JsonAssert.assertJson; public class ShowActionTest { + private static MetricDto dataMetric = RegisterMetrics.MetricToDto.INSTANCE.apply(CoreMetrics.DUPLICATIONS_DATA); + @Rule public ExpectedException expectedException = ExpectedException.none(); @Rule public UserSessionRule userSessionRule = UserSessionRule.standalone(); + @Rule public DbTester db = DbTester.create(); - private DuplicationsParser parser = new DuplicationsParser(db.getDbClient().componentDao()); - private DuplicationsJsonWriter duplicationsJsonWriter = new DuplicationsJsonWriter(db.getDbClient().componentDao()); - private WsTester tester; - private MetricDto dataMetric = RegisterMetrics.MetricToDto.INSTANCE.apply(CoreMetrics.DUPLICATIONS_DATA); + private ShowResponseBuilder showResponseBuilder = new ShowResponseBuilder(db.getDbClient()); + + private WsActionTester ws = new WsActionTester(new ShowAction(db.getDbClient(), parser, showResponseBuilder, userSessionRule, TestComponentFinder.from(db))); @Before public void setUp() { - tester = new WsTester(new DuplicationsWs(new ShowAction(db.getDbClient(), parser, duplicationsJsonWriter, userSessionRule, TestComponentFinder.from(db)))); - db.getDbClient().metricDao().insert(db.getSession(), dataMetric); db.commit(); } @Test public void get_duplications_by_file_key() throws Exception { - WsTester.TestRequest request = newBaseRequest(); + TestRequest request = newBaseRequest(); verifyCallToFileWithDuplications(file -> request.setParam("key", file.key())); } @Test public void get_duplications_by_file_id() throws Exception { - WsTester.TestRequest request = newBaseRequest(); + TestRequest request = newBaseRequest(); verifyCallToFileWithDuplications(file -> request.setParam("uuid", file.uuid())); } @@ -83,23 +86,23 @@ public class ShowActionTest { userSessionRule.addProjectPermission(UserRole.CODEVIEWER, project); - WsTester.Result result = newBaseRequest().setParam("key", file.key()).execute(); + TestResponse result = newBaseRequest().setParam("key", file.key()).execute(); - result.assertJson("{\n" + + assertJson(result.getInput()).isSimilarTo("{\n" + " \"duplications\": [],\n" + " \"files\": {}\n" + "}"); } @Test - public void return_404_if_file_does_not_exist() throws Exception { + public void fail_if_file_does_not_exist() throws Exception { expectedException.expect(NotFoundException.class); newBaseRequest().setParam("key", "missing").execute(); } @Test - public void return_403_if_user_is_not_allowed_to_access_project() throws Exception { + public void fail_if_user_is_not_allowed_to_access_project() throws Exception { ComponentDto project = db.components().insertPrivateProject(); ComponentDto file = db.components().insertComponent(newFileDto(project)); @@ -108,11 +111,19 @@ public class ShowActionTest { newBaseRequest().setParam("key", file.key()).execute(); } - private WsTester.TestRequest newBaseRequest() { - return tester.newGetRequest("api/duplications", "show"); + @Test + public void fail_if_no_parameter_provided() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Either 'uuid' or 'key' must be provided, not both"); + + newBaseRequest().execute(); + } + + private TestRequest newBaseRequest() { + return ws.newRequest(); } - private void verifyCallToFileWithDuplications(Function requestFactory) throws Exception { + private void verifyCallToFileWithDuplications(Function requestFactory) throws Exception { ComponentDto project = db.components().insertPrivateProject(); ComponentDto file = db.components().insertComponent(newFileDto(project).setKey("foo.js")); SnapshotDto snapshot = db.components().insertSnapshot(newAnalysis(project)); @@ -127,10 +138,10 @@ public class ShowActionTest { userSessionRule.addProjectPermission(UserRole.CODEVIEWER, project); - WsTester.TestRequest request = requestFactory.apply(file); - WsTester.Result result = request.execute(); + TestRequest request = requestFactory.apply(file); + TestResponse result = request.execute(); - result.assertJson("{\"duplications\":[" + + assertJson(result.getInput()).isSimilarTo("{\"duplications\":[" + "{\"blocks\":[{\"from\":20,\"size\":5,\"_ref\":\"1\"},{\"from\":31,\"size\":5,\"_ref\":\"1\"}]}]," + "\"files\":{\"1\":{\"key\":\"foo.js\",\"uuid\":\"" + file.uuid() + "\"}}}"); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/DuplicationsJsonWriterTest.java b/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/ShowResponseBuilderTest.java similarity index 76% rename from server/sonar-server/src/test/java/org/sonar/server/duplication/ws/DuplicationsJsonWriterTest.java rename to server/sonar-server/src/test/java/org/sonar/server/duplication/ws/ShowResponseBuilderTest.java index d51dcc4397c..8dfc52367bf 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/DuplicationsJsonWriterTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/ShowResponseBuilderTest.java @@ -19,67 +19,63 @@ */ package org.sonar.server.duplication.ws; -import com.google.common.base.Optional; import java.io.StringWriter; import java.util.Collections; import java.util.List; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; import org.sonar.api.utils.text.JsonWriter; +import org.sonar.core.util.ProtobufJsonFormat; import org.sonar.db.DbSession; +import org.sonar.db.DbTester; import org.sonar.db.component.ComponentDao; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentTesting; +import org.sonar.db.organization.OrganizationDto; import org.sonar.db.organization.OrganizationTesting; import org.sonar.test.JsonAssert; import static com.google.common.collect.Lists.newArrayList; -import static org.mockito.Matchers.anyLong; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto; -@RunWith(MockitoJUnitRunner.class) -public class DuplicationsJsonWriterTest { +public class ShowResponseBuilderTest { - @Mock - ComponentDao componentDao; + @Rule + public DbTester db = DbTester.create(); + private DbSession dbSession = db.getSession(); + private ComponentDao componentDao = spy(db.getDbClient().componentDao()); - @Mock - DbSession session; + private ComponentDto project; + private OrganizationDto organization = OrganizationTesting.newOrganizationDto(); - DuplicationsJsonWriter writer; - - ComponentDto project; + private ShowResponseBuilder underTest = new ShowResponseBuilder(componentDao); @Before public void setUp() { - project = ComponentTesting.newPrivateProjectDto(OrganizationTesting.newOrganizationDto()) - .setId(1L) + project = newPrivateProjectDto(organization) .setName("SonarQube") .setLongName("SonarQube") .setKey("org.codehaus.sonar:sonar"); - - writer = new DuplicationsJsonWriter(componentDao); + db.components().insertComponent(project); } @Test public void write_duplications() { String key1 = "org.codehaus.sonar:sonar-ws-client:src/main/java/org/sonar/wsclient/services/PropertyDeleteQuery.java"; - ComponentDto file1 = ComponentTesting.newFileDto(project, null).setId(10L).setKey(key1).setLongName("PropertyDeleteQuery").setRootUuid("uuid_5"); + ComponentDto file1 = ComponentTesting.newFileDto(project, null).setKey(key1).setLongName("PropertyDeleteQuery").setRootUuid("uuid_5"); String key2 = "org.codehaus.sonar:sonar-ws-client:src/main/java/org/sonar/wsclient/services/PropertyUpdateQuery.java"; - ComponentDto file2 = ComponentTesting.newFileDto(project, null).setId(11L).setQualifier("FIL").setKey(key2).setLongName("PropertyUpdateQuery").setRootUuid("uuid_5"); + ComponentDto file2 = ComponentTesting.newFileDto(project, null).setQualifier("FIL").setKey(key2).setLongName("PropertyUpdateQuery").setRootUuid("uuid_5"); + ComponentDto project2 = db.components().insertPrivateProject(organization, p -> p.setUuid("uuid_5").setKey("org.codehaus.sonar:sonar-ws-client") + .setLongName("SonarQube :: Web Service Client")); - when(componentDao.selectByKey(session, key1)).thenReturn(Optional.of(file1)); - when(componentDao.selectByKey(session, key2)).thenReturn(Optional.of(file2)); - when(componentDao.selectByUuid(session, "uuid_5")).thenReturn(Optional.of( - new ComponentDto().setUuid("uuid_5").setKey("org.codehaus.sonar:sonar-ws-client").setLongName("SonarQube :: Web Service Client"))); - when(componentDao.selectByUuid(session, project.uuid())).thenReturn(Optional.of(project)); + db.components().insertComponent(file1); + db.components().insertComponent(file2); List blocks = newArrayList(); blocks.add(new DuplicationsParser.Block(newArrayList( @@ -121,10 +117,10 @@ public class DuplicationsJsonWriterTest { " }" + "}"); - verify(componentDao, times(2)).selectByKey(eq(session), anyString()); + verify(componentDao, times(2)).selectByKey(eq(dbSession), anyString()); // Verify call to dao is cached when searching for project / sub project - verify(componentDao, times(1)).selectByUuid(eq(session), eq(project.uuid())); - verify(componentDao, times(1)).selectByUuid(eq(session), eq("uuid_5")); + verify(componentDao, times(1)).selectByUuid(eq(dbSession), eq(project.uuid())); + verify(componentDao, times(1)).selectByUuid(eq(dbSession), eq("uuid_5")); } @Test @@ -134,10 +130,8 @@ public class DuplicationsJsonWriterTest { String key2 = "org.codehaus.sonar:sonar-ws-client:src/main/java/org/sonar/wsclient/services/PropertyUpdateQuery.java"; ComponentDto file2 = ComponentTesting.newFileDto(project, null).setId(11L).setKey(key2).setLongName("PropertyUpdateQuery"); - when(componentDao.selectByKey(session, key1)).thenReturn(Optional.of(file1)); - when(componentDao.selectByKey(session, key2)).thenReturn(Optional.of(file2)); - when(componentDao.selectById(eq(session), anyLong())).thenReturn(Optional.absent()); - when(componentDao.selectByUuid(session, project.uuid())).thenReturn(Optional.of(project)); + db.components().insertComponent(file1); + db.components().insertComponent(file2); List blocks = newArrayList(); blocks.add(new DuplicationsParser.Block(newArrayList( @@ -180,10 +174,7 @@ public class DuplicationsJsonWriterTest { public void write_duplications_with_a_removed_component() { String key1 = "org.codehaus.sonar:sonar-ws-client:src/main/java/org/sonar/wsclient/services/PropertyDeleteQuery.java"; ComponentDto file1 = ComponentTesting.newFileDto(project, null).setId(10L).setKey(key1).setLongName("PropertyDeleteQuery"); - - when(componentDao.selectByKey(session, key1)).thenReturn(Optional.of(file1)); - when(componentDao.selectByUuid(session, project.uuid())).thenReturn(Optional.of(project)); - when(componentDao.selectById(eq(session), anyLong())).thenReturn(Optional.absent()); + db.components().insertComponent(file1); List blocks = newArrayList(); @@ -220,15 +211,13 @@ public class DuplicationsJsonWriterTest { @Test public void write_nothing_when_no_data() { - test(Collections.emptyList(), "{\"duplications\": [], \"files\": {}}"); + test(Collections.emptyList(), "{\"duplications\": [], \"files\": {}}"); } private void test(List blocks, String expected) { StringWriter output = new StringWriter(); JsonWriter jsonWriter = JsonWriter.of(output); - jsonWriter.beginObject(); - writer.write(blocks, jsonWriter, session); - jsonWriter.endObject(); + ProtobufJsonFormat.write(underTest.build(blocks, dbSession), jsonWriter); JsonAssert.assertJson(output.toString()).isSimilarTo(expected); } diff --git a/sonar-ws/src/main/protobuf/ws-duplications.proto b/sonar-ws/src/main/protobuf/ws-duplications.proto new file mode 100644 index 00000000000..f8b292ec4e8 --- /dev/null +++ b/sonar-ws/src/main/protobuf/ws-duplications.proto @@ -0,0 +1,54 @@ +// SonarQube, open source software quality management tool. +// Copyright (C) 2008-2016 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. + +syntax = "proto3"; + +package sonarqube.ws.duplication; + +option java_package = "org.sonarqube.ws"; +option java_outer_classname = "WsDuplications"; +option optimize_for = SPEED; + +// WS api/duplications/show +message ShowResponse { + repeated Duplication duplications = 1; + map files = 2; + +} + +message Duplication { + repeated Block blocks = 1; +} + +message Block { + int32 from = 1; + int32 size = 2; + string _ref = 3; +} + +message File { + string key = 1; + string name = 2; + string uuid = 3; + string project = 4; + string projectUuid = 5; + string projectName = 6; + string subProject = 7; + string subProjectUuid = 8; + string subProjectName = 9; +} -- 2.39.5