From: Teryk Bellahsene Date: Tue, 20 Jun 2017 09:41:57 +0000 (+0200) Subject: SONAR-9441 WS api/duplications/show fails properly when no parameter provided X-Git-Tag: 6.5-M2~113 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=25e95eda0c4c00bf5a92543db8b5e990f901d117;p=sonarqube.git SONAR-9441 WS api/duplications/show fails properly when no parameter provided --- 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/DuplicationsJsonWriter.java b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsJsonWriter.java deleted file mode 100644 index 4dc6513260e..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsJsonWriter.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * 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 com.google.common.annotations.VisibleForTesting; -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.DbSession; -import org.sonar.db.component.ComponentDao; -import org.sonar.db.component.ComponentDto; - -import static com.google.common.collect.Maps.newHashMap; - -@ServerSide -public class DuplicationsJsonWriter { - - private final ComponentDao componentDao; - - public DuplicationsJsonWriter(ComponentDao componentDao) { - this.componentDao = componentDao; - } - - @VisibleForTesting - void write(List blocks, JsonWriter json, DbSession session) { - Map refByComponentKey = newHashMap(); - json.name("duplications").beginArray(); - writeDuplications(blocks, refByComponentKey, json); - json.endArray(); - - json.name("files").beginObject(); - writeFiles(refByComponentKey, json, session); - json.endObject(); - } - - 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 void writeDuplication(Map refByComponentKey, DuplicationsParser.Duplication duplication, JsonWriter json) { - String ref = null; - ComponentDto componentDto = duplication.file(); - if (componentDto != null) { - String componentKey = componentDto.key(); - ref = refByComponentKey.get(componentKey); - if (ref == null) { - ref = Integer.toString(refByComponentKey.size() + 1); - refByComponentKey.put(componentKey, ref); - } - } - - json.beginObject(); - json.prop("from", duplication.from()); - json.prop("size", duplication.size()); - json.prop("_ref", ref); - json.endObject(); - } - - private void writeFiles(Map refByComponentKey, JsonWriter json, DbSession session) { - Map projectsByUuid = newHashMap(); - Map parentProjectsByUuid = newHashMap(); - 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()); - } - } - } - - private ComponentDto getProject(String projectUuid, Map projectsByUuid, DbSession session) { - ComponentDto project = projectsByUuid.get(projectUuid); - if (project == null) { - Optional projectOptional = componentDao.selectByUuid(session, projectUuid); - if (projectOptional.isPresent()) { - project = projectOptional.get(); - projectsByUuid.put(project.uuid(), project); - } - } - return project; - } - - private ComponentDto getParentProject(String rootUuid, Map subProjectsByUuid, DbSession session) { - ComponentDto project = subProjectsByUuid.get(rootUuid); - if (project == null) { - Optional projectOptional = componentDao.selectByUuid(session, rootUuid); - if (projectOptional.isPresent()) { - project = projectOptional.get(); - subProjectsByUuid.put(project.uuid(), project); - } - } - return project; - } - -} 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/ShowResponseBuilder.java b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowResponseBuilder.java new file mode 100644 index 00000000000..9e61f607405 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowResponseBuilder.java @@ -0,0 +1,158 @@ +/* + * 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 com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Optional; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +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; + +public class ShowResponseBuilder { + + private final ComponentDao componentDao; + + public ShowResponseBuilder(DbClient dbClient) { + this.componentDao = dbClient.componentDao(); + } + + @VisibleForTesting + ShowResponseBuilder(ComponentDao componentDao) { + this.componentDao = componentDao; + } + + ShowResponse build(List blocks, DbSession session) { + ShowResponse.Builder response = ShowResponse.newBuilder(); + Map refByComponentKey = newHashMap(); + blocks.stream() + .map(block -> toWsDuplication(block, refByComponentKey)) + .forEach(response::addDuplications); + + writeFiles(response, refByComponentKey, session); + + return response.build(); + } + + 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 Block.Builder toWsBlock(Map refByComponentKey, DuplicationsParser.Duplication duplication) { + String ref = null; + ComponentDto componentDto = duplication.file(); + if (componentDto != null) { + String componentKey = componentDto.key(); + ref = refByComponentKey.get(componentKey); + if (ref == null) { + ref = Integer.toString(refByComponentKey.size() + 1); + refByComponentKey.put(componentKey, ref); + } + } + + Block.Builder block = Block.newBuilder(); + block.setFrom(duplication.from()); + block.setSize(duplication.size()); + setNullable(ref, block::setRef); + + return block; + } + + 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 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(); + + ComponentDto project = getProject(file.projectUuid(), projectsByUuid, session); + ComponentDto parentModule = getParentProject(file.getRootUuid(), parentModulesByUuid, session); + filesByRef.put(ref, toWsFile(file, project, parentModule)); + } + } + } + + private ComponentDto getProject(String projectUuid, Map projectsByUuid, DbSession session) { + ComponentDto project = projectsByUuid.get(projectUuid); + if (project == null) { + Optional projectOptional = componentDao.selectByUuid(session, projectUuid); + if (projectOptional.isPresent()) { + project = projectOptional.get(); + projectsByUuid.put(project.uuid(), project); + } + } + return project; + } + + private ComponentDto getParentProject(String rootUuid, Map subProjectsByUuid, DbSession session) { + ComponentDto project = subProjectsByUuid.get(rootUuid); + if (project == null) { + Optional projectOptional = componentDao.selectByUuid(session, rootUuid); + if (projectOptional.isPresent()) { + project = projectOptional.get(); + subProjectsByUuid.put(project.uuid(), project); + } + } + return project; + } + +} 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/example-show.json deleted file mode 100644 index 16be68f375b..00000000000 --- a/server/sonar-server/src/main/resources/org/sonar/server/duplication/ws/example-show.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "duplications": [ - { - "blocks": [ - { - "from": 94, "size": 101, "_ref": "1" - }, - { - "from": 83, "size": 101, "_ref": "2" - } - ] - }, - { - "blocks": [ - { - "from": 38, "size": 40, "_ref": "1" - }, - { - "from": 29, "size": 39, "_ref": "2" - } - ] - }, - { - "blocks": [ - { - "from": 148, "size": 24, "_ref": "1" - }, - { - "from": 137, "size": 24, "_ref": "2" - }, - { - "from": 137, "size": 24, "_ref": "3" - } - ] - } - ], - "files": { - "1": { - "key": "org.codehaus.sonar:sonar-plugin-api:src/main/java/org/sonar/api/utils/command/CommandExecutor.java", - "name": "CommandExecutor", - "projectName": "SonarQube" - }, - "2": { - "key": "com.sonarsource.orchestrator:sonar-orchestrator:src/main/java/com/sonar/orchestrator/util/CommandExecutor.java", - "name": "CommandExecutor", - "projectName": "SonarSource :: Orchestrator" - }, - "3": { - "key": "org.codehaus.sonar.runner:sonar-runner-api:src/main/java/org/sonar/runner/api/CommandExecutor.java", - "name": "CommandExecutor", - "projectName": "SonarSource Runner" - } - } -} diff --git a/server/sonar-server/src/main/resources/org/sonar/server/duplication/ws/show-example.json b/server/sonar-server/src/main/resources/org/sonar/server/duplication/ws/show-example.json new file mode 100644 index 00000000000..64f3fb78903 --- /dev/null +++ b/server/sonar-server/src/main/resources/org/sonar/server/duplication/ws/show-example.json @@ -0,0 +1,68 @@ +{ + "duplications": [ + { + "blocks": [ + { + "from": 94, + "size": 101, + "_ref": "1" + }, + { + "from": 83, + "size": 101, + "_ref": "2" + } + ] + }, + { + "blocks": [ + { + "from": 38, + "size": 40, + "_ref": "1" + }, + { + "from": 29, + "size": 39, + "_ref": "2" + } + ] + }, + { + "blocks": [ + { + "from": 148, + "size": 24, + "_ref": "1" + }, + { + "from": 137, + "size": 24, + "_ref": "2" + }, + { + "from": 137, + "size": 24, + "_ref": "3" + } + ] + } + ], + "files": { + "1": { + "key": "org.codehaus.sonar:sonar-plugin-api:src/main/java/org/sonar/api/utils/command/CommandExecutor.java", + "name": "CommandExecutor", + "projectName": "SonarQube" + }, + "2": { + "key": "com.sonarsource.orchestrator:sonar-orchestrator:src/main/java/com/sonar/orchestrator/util/CommandExecutor.java", + "name": "CommandExecutor", + "projectName": "SonarSource :: Orchestrator" + }, + "3": { + "key": "org.codehaus.sonar.runner:sonar-runner-api:src/main/java/org/sonar/runner/api/CommandExecutor.java", + "name": "CommandExecutor", + "projectName": "SonarSource Runner" + } + } +} 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/DuplicationsJsonWriterTest.java deleted file mode 100644 index d51dcc4397c..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/DuplicationsJsonWriterTest.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * 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 com.google.common.base.Optional; -import java.io.StringWriter; -import java.util.Collections; -import java.util.List; -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.api.utils.text.JsonWriter; -import org.sonar.db.DbSession; -import org.sonar.db.component.ComponentDao; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.component.ComponentTesting; -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.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class DuplicationsJsonWriterTest { - - @Mock - ComponentDao componentDao; - - @Mock - DbSession session; - - DuplicationsJsonWriter writer; - - ComponentDto project; - - @Before - public void setUp() { - project = ComponentTesting.newPrivateProjectDto(OrganizationTesting.newOrganizationDto()) - .setId(1L) - .setName("SonarQube") - .setLongName("SonarQube") - .setKey("org.codehaus.sonar:sonar"); - - writer = new DuplicationsJsonWriter(componentDao); - } - - @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"); - 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"); - - 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)); - - List blocks = newArrayList(); - blocks.add(new DuplicationsParser.Block(newArrayList( - new DuplicationsParser.Duplication(file1, 57, 12), - new DuplicationsParser.Duplication(file2, 73, 12) - ))); - - test(blocks, - "{\n" + - " \"duplications\": [\n" + - " {\n" + - " \"blocks\": [\n" + - " {\n" + - " \"from\": 57, \"size\": 12, \"_ref\": \"1\"\n" + - " },\n" + - " {\n" + - " \"from\": 73, \"size\": 12, \"_ref\": \"2\"\n" + - " }\n" + - " ]\n" + - " }," + - " ],\n" + - " \"files\": {\n" + - " \"1\": {\n" + - " \"key\": \"org.codehaus.sonar:sonar-ws-client:src/main/java/org/sonar/wsclient/services/PropertyDeleteQuery.java\",\n" + - " \"name\": \"PropertyDeleteQuery\",\n" + - " \"project\": \"org.codehaus.sonar:sonar\",\n" + - " \"projectName\": \"SonarQube\",\n" + - " \"subProject\": \"org.codehaus.sonar:sonar-ws-client\",\n" + - " \"subProjectName\": \"SonarQube :: Web Service Client\"\n" + - " },\n" + - " \"2\": {\n" + - " \"key\": \"org.codehaus.sonar:sonar-ws-client:src/main/java/org/sonar/wsclient/services/PropertyUpdateQuery.java\",\n" + - " \"name\": \"PropertyUpdateQuery\",\n" + - " \"project\": \"org.codehaus.sonar:sonar\",\n" + - " \"projectName\": \"SonarQube\",\n" + - " \"subProject\": \"org.codehaus.sonar:sonar-ws-client\",\n" + - " \"subProjectName\": \"SonarQube :: Web Service Client\"\n" + - " }\n" + - " }" + - "}"); - - verify(componentDao, times(2)).selectByKey(eq(session), 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")); - } - - @Test - public void write_duplications_without_sub_project() { - 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"); - 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)); - - List blocks = newArrayList(); - blocks.add(new DuplicationsParser.Block(newArrayList( - new DuplicationsParser.Duplication(file1, 57, 12), - new DuplicationsParser.Duplication(file2, 73, 12) - ))); - - test(blocks, - "{\n" + - " \"duplications\": [\n" + - " {\n" + - " \"blocks\": [\n" + - " {\n" + - " \"from\": 57, \"size\": 12, \"_ref\": \"1\"\n" + - " },\n" + - " {\n" + - " \"from\": 73, \"size\": 12, \"_ref\": \"2\"\n" + - " }\n" + - " ]\n" + - " }," + - " ],\n" + - " \"files\": {\n" + - " \"1\": {\n" + - " \"key\": \"org.codehaus.sonar:sonar-ws-client:src/main/java/org/sonar/wsclient/services/PropertyDeleteQuery.java\",\n" + - " \"name\": \"PropertyDeleteQuery\",\n" + - " \"project\": \"org.codehaus.sonar:sonar\",\n" + - " \"projectName\": \"SonarQube\"\n" + - " },\n" + - " \"2\": {\n" + - " \"key\": \"org.codehaus.sonar:sonar-ws-client:src/main/java/org/sonar/wsclient/services/PropertyUpdateQuery.java\",\n" + - " \"name\": \"PropertyUpdateQuery\",\n" + - " \"project\": \"org.codehaus.sonar:sonar\",\n" + - " \"projectName\": \"SonarQube\"\n" + - " }\n" + - " }" + - "}"); - } - - @Test - 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()); - - List blocks = newArrayList(); - - blocks.add(new DuplicationsParser.Block(newArrayList( - new DuplicationsParser.Duplication(file1, 57, 12), - // Duplication on a removed file - new DuplicationsParser.Duplication(null, 73, 12) - ))); - - test(blocks, - "{\n" + - " \"duplications\": [\n" + - " {\n" + - " \"blocks\": [\n" + - " {\n" + - " \"from\": 57, \"size\": 12, \"_ref\": \"1\"\n" + - " },\n" + - " {\n" + - " \"from\": 73, \"size\": 12\n" + - " }\n" + - " ]\n" + - " }," + - " ],\n" + - " \"files\": {\n" + - " \"1\": {\n" + - " \"key\": \"org.codehaus.sonar:sonar-ws-client:src/main/java/org/sonar/wsclient/services/PropertyDeleteQuery.java\",\n" + - " \"name\": \"PropertyDeleteQuery\",\n" + - " \"project\": \"org.codehaus.sonar:sonar\",\n" + - " \"projectName\": \"SonarQube\"\n" + - " }\n" + - " }" + - "}"); - } - - @Test - public void write_nothing_when_no_data() { - 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(); - JsonAssert.assertJson(output.toString()).isSimilarTo(expected); - } - -} 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/ShowResponseBuilderTest.java b/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/ShowResponseBuilderTest.java new file mode 100644 index 00000000000..8dfc52367bf --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/ShowResponseBuilderTest.java @@ -0,0 +1,224 @@ +/* + * 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 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.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.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.sonar.db.component.ComponentTesting.newPrivateProjectDto; + +public class ShowResponseBuilderTest { + + @Rule + public DbTester db = DbTester.create(); + private DbSession dbSession = db.getSession(); + private ComponentDao componentDao = spy(db.getDbClient().componentDao()); + + private ComponentDto project; + private OrganizationDto organization = OrganizationTesting.newOrganizationDto(); + + private ShowResponseBuilder underTest = new ShowResponseBuilder(componentDao); + + @Before + public void setUp() { + project = newPrivateProjectDto(organization) + .setName("SonarQube") + .setLongName("SonarQube") + .setKey("org.codehaus.sonar:sonar"); + 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).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).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")); + + db.components().insertComponent(file1); + db.components().insertComponent(file2); + + List blocks = newArrayList(); + blocks.add(new DuplicationsParser.Block(newArrayList( + new DuplicationsParser.Duplication(file1, 57, 12), + new DuplicationsParser.Duplication(file2, 73, 12) + ))); + + test(blocks, + "{\n" + + " \"duplications\": [\n" + + " {\n" + + " \"blocks\": [\n" + + " {\n" + + " \"from\": 57, \"size\": 12, \"_ref\": \"1\"\n" + + " },\n" + + " {\n" + + " \"from\": 73, \"size\": 12, \"_ref\": \"2\"\n" + + " }\n" + + " ]\n" + + " }," + + " ],\n" + + " \"files\": {\n" + + " \"1\": {\n" + + " \"key\": \"org.codehaus.sonar:sonar-ws-client:src/main/java/org/sonar/wsclient/services/PropertyDeleteQuery.java\",\n" + + " \"name\": \"PropertyDeleteQuery\",\n" + + " \"project\": \"org.codehaus.sonar:sonar\",\n" + + " \"projectName\": \"SonarQube\",\n" + + " \"subProject\": \"org.codehaus.sonar:sonar-ws-client\",\n" + + " \"subProjectName\": \"SonarQube :: Web Service Client\"\n" + + " },\n" + + " \"2\": {\n" + + " \"key\": \"org.codehaus.sonar:sonar-ws-client:src/main/java/org/sonar/wsclient/services/PropertyUpdateQuery.java\",\n" + + " \"name\": \"PropertyUpdateQuery\",\n" + + " \"project\": \"org.codehaus.sonar:sonar\",\n" + + " \"projectName\": \"SonarQube\",\n" + + " \"subProject\": \"org.codehaus.sonar:sonar-ws-client\",\n" + + " \"subProjectName\": \"SonarQube :: Web Service Client\"\n" + + " }\n" + + " }" + + "}"); + + 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(dbSession), eq(project.uuid())); + verify(componentDao, times(1)).selectByUuid(eq(dbSession), eq("uuid_5")); + } + + @Test + public void write_duplications_without_sub_project() { + 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"); + 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"); + + db.components().insertComponent(file1); + db.components().insertComponent(file2); + + List blocks = newArrayList(); + blocks.add(new DuplicationsParser.Block(newArrayList( + new DuplicationsParser.Duplication(file1, 57, 12), + new DuplicationsParser.Duplication(file2, 73, 12) + ))); + + test(blocks, + "{\n" + + " \"duplications\": [\n" + + " {\n" + + " \"blocks\": [\n" + + " {\n" + + " \"from\": 57, \"size\": 12, \"_ref\": \"1\"\n" + + " },\n" + + " {\n" + + " \"from\": 73, \"size\": 12, \"_ref\": \"2\"\n" + + " }\n" + + " ]\n" + + " }," + + " ],\n" + + " \"files\": {\n" + + " \"1\": {\n" + + " \"key\": \"org.codehaus.sonar:sonar-ws-client:src/main/java/org/sonar/wsclient/services/PropertyDeleteQuery.java\",\n" + + " \"name\": \"PropertyDeleteQuery\",\n" + + " \"project\": \"org.codehaus.sonar:sonar\",\n" + + " \"projectName\": \"SonarQube\"\n" + + " },\n" + + " \"2\": {\n" + + " \"key\": \"org.codehaus.sonar:sonar-ws-client:src/main/java/org/sonar/wsclient/services/PropertyUpdateQuery.java\",\n" + + " \"name\": \"PropertyUpdateQuery\",\n" + + " \"project\": \"org.codehaus.sonar:sonar\",\n" + + " \"projectName\": \"SonarQube\"\n" + + " }\n" + + " }" + + "}"); + } + + @Test + 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"); + db.components().insertComponent(file1); + + List blocks = newArrayList(); + + blocks.add(new DuplicationsParser.Block(newArrayList( + new DuplicationsParser.Duplication(file1, 57, 12), + // Duplication on a removed file + new DuplicationsParser.Duplication(null, 73, 12) + ))); + + test(blocks, + "{\n" + + " \"duplications\": [\n" + + " {\n" + + " \"blocks\": [\n" + + " {\n" + + " \"from\": 57, \"size\": 12, \"_ref\": \"1\"\n" + + " },\n" + + " {\n" + + " \"from\": 73, \"size\": 12\n" + + " }\n" + + " ]\n" + + " }," + + " ],\n" + + " \"files\": {\n" + + " \"1\": {\n" + + " \"key\": \"org.codehaus.sonar:sonar-ws-client:src/main/java/org/sonar/wsclient/services/PropertyDeleteQuery.java\",\n" + + " \"name\": \"PropertyDeleteQuery\",\n" + + " \"project\": \"org.codehaus.sonar:sonar\",\n" + + " \"projectName\": \"SonarQube\"\n" + + " }\n" + + " }" + + "}"); + } + + @Test + public void write_nothing_when_no_data() { + test(Collections.emptyList(), "{\"duplications\": [], \"files\": {}}"); + } + + private void test(List blocks, String expected) { + StringWriter output = new StringWriter(); + JsonWriter jsonWriter = JsonWriter.of(output); + 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; +}