From: Julien Lancelot Date: Mon, 27 Apr 2015 14:36:40 +0000 (+0200) Subject: Add key in /api/sources/lines WS X-Git-Tag: 5.2-RC1~2113 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=5a94ef9310cb7049170563d919b900a95c9059f4;p=sonarqube.git Add key in /api/sources/lines WS --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/source/ws/LinesAction.java b/server/sonar-server/src/main/java/org/sonar/server/source/ws/LinesAction.java index c5fb6cf9a01..ff750c5c984 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/source/ws/LinesAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/source/ws/LinesAction.java @@ -28,7 +28,9 @@ import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.text.JsonWriter; import org.sonar.api.web.UserRole; import org.sonar.core.component.ComponentDto; -import org.sonar.server.component.ComponentService; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.persistence.MyBatis; +import org.sonar.server.db.DbClient; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.source.HtmlSourceDecorator; import org.sonar.server.source.index.SourceLineDoc; @@ -40,14 +42,17 @@ import java.util.List; public class LinesAction implements SourcesAction { + private static final String PARAM_UUID = "uuid"; + private static final String PARAM_KEY = "key"; + private final SourceLineIndex sourceLineIndex; private final HtmlSourceDecorator htmlSourceDecorator; - private final ComponentService componentService; + private final DbClient dbClient; - public LinesAction(SourceLineIndex sourceLineIndex, HtmlSourceDecorator htmlSourceDecorator, ComponentService componentService) { + public LinesAction(DbClient dbClient, SourceLineIndex sourceLineIndex, HtmlSourceDecorator htmlSourceDecorator) { this.sourceLineIndex = sourceLineIndex; this.htmlSourceDecorator = htmlSourceDecorator; - this.componentService = componentService; + this.dbClient = dbClient; } @Override @@ -74,11 +79,15 @@ public class LinesAction implements SourcesAction { .setHandler(this); action - .createParam("uuid") - .setRequired(true) - .setDescription("File uuid") + .createParam(PARAM_UUID) + .setDescription("File uuid. Mandatory if param 'key' is not set") .setExampleValue("f333aab4-7e3a-4d70-87e1-f4c491f05e5c"); + action + .createParam(PARAM_KEY) + .setDescription("File key. Mandatory if param 'uuid' is not set. Available since 5.2") + .setExampleValue("org.sample:src/main/java/Foo.java"); + action .createParam("from") .setDescription("First line to return. Starts at 1") @@ -93,16 +102,15 @@ public class LinesAction implements SourcesAction { @Override public void handle(Request request, Response response) { - String fileUuid = request.mandatoryParam("uuid"); - ComponentDto component = componentService.getByUuid(fileUuid); + ComponentDto component = loadComponent(request); UserSession.get().checkProjectUuidPermission(UserRole.CODEVIEWER, component.projectUuid()); int from = Math.max(request.mandatoryParamAsInt("from"), 1); int to = (Integer) ObjectUtils.defaultIfNull(request.paramAsInt("to"), Integer.MAX_VALUE); - List sourceLines = sourceLineIndex.getLines(fileUuid, from, to); + List sourceLines = sourceLineIndex.getLines(component.uuid(), from, to); if (sourceLines.isEmpty()) { - throw new NotFoundException("File '" + fileUuid + "' has no sources"); + throw new NotFoundException("File '" + component.key() + "' has no sources"); } JsonWriter json = response.newJsonWriter().beginObject(); @@ -113,7 +121,7 @@ public class LinesAction implements SourcesAction { private void writeSource(List lines, JsonWriter json) { json.name("sources").beginArray(); - for (SourceLineDoc line: lines) { + for (SourceLineDoc line : lines) { json.beginObject() .prop("line", line.line()) .prop("code", htmlSourceDecorator.getDecoratedSourceAsHtml(line.source(), line.highlighting(), line.symbols())) @@ -127,11 +135,29 @@ public class LinesAction implements SourcesAction { .prop("itLineHits", line.itLineHits()) .prop("itConditions", line.itConditions()) .prop("itCoveredConditions", line.itCoveredConditions()); - if (! line.duplications().isEmpty()) { + if (!line.duplications().isEmpty()) { json.prop("duplicated", true); } json.endObject(); } json.endArray(); } + + private ComponentDto loadComponent(Request request) { + DbSession session = dbClient.openSession(false); + try { + String fileUuid = request.param(PARAM_UUID); + if (fileUuid != null) { + return dbClient.componentDao().getByUuid(session, fileUuid); + } + String fileKey = request.param(PARAM_KEY); + if (fileKey != null) { + return dbClient.componentDao().getByKey(session, fileKey); + } + throw new IllegalArgumentException(String.format("Param %s or param %s is missing", PARAM_UUID, PARAM_KEY)); + } finally { + MyBatis.closeQuietly(session); + } + } + } diff --git a/server/sonar-server/src/test/java/org/sonar/server/source/ws/LinesActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/source/ws/LinesActionTest.java index 7dbbbb30cfb..f8a3825b08e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/source/ws/LinesActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/source/ws/LinesActionTest.java @@ -17,21 +17,23 @@ * 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.source.ws; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; -import org.apache.commons.lang.StringEscapeUtils; +import org.junit.After; import org.junit.Before; +import org.junit.ClassRule; import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.runners.MockitoJUnitRunner; -import org.mockito.stubbing.Answer; +import org.sonar.api.config.Settings; import org.sonar.api.web.UserRole; import org.sonar.core.component.ComponentDto; -import org.sonar.server.component.ComponentService; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.persistence.DbTester; +import org.sonar.server.component.ComponentTesting; +import org.sonar.server.component.db.ComponentDao; +import org.sonar.server.db.DbClient; +import org.sonar.server.es.EsTester; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.source.HtmlSourceDecorator; @@ -43,54 +45,67 @@ import org.sonar.server.ws.WsTester; import java.util.Date; -import static com.google.common.collect.Lists.newArrayList; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; import static org.junit.Assert.fail; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.when; -@RunWith(MockitoJUnitRunner.class) public class LinesActionTest { - @Mock + private static final String PROJECT_UUID = "abcd"; + private static final String FILE_UUID = "efgh"; + private static final String FILE_KEY = "Foo.java"; + + @ClassRule + public static EsTester esTester = new EsTester().addDefinitions(new SourceLineIndexDefinition(new Settings())); + + @ClassRule + public static DbTester dbTester = new DbTester(); + SourceLineIndex sourceLineIndex; - @Mock HtmlSourceDecorator htmlSourceDecorator; - @Mock - ComponentService componentService; + ComponentDao componentDao; + + DbSession session; - WsTester tester; + WsTester wsTester; @Before public void setUp() throws Exception { - tester = new WsTester(new SourcesWs(new LinesAction(sourceLineIndex, htmlSourceDecorator, componentService))); - when(htmlSourceDecorator.getDecoratedSourceAsHtml(anyString(), anyString(), anyString())).thenAnswer(new Answer() { - @Override - public String answer(InvocationOnMock invocation) throws Throwable { - return "" + - StringEscapeUtils.escapeHtml((String) invocation.getArguments()[0]) + - ""; - } - }); + dbTester.truncateTables(); + esTester.truncateIndices(); + + htmlSourceDecorator = new HtmlSourceDecorator(); + sourceLineIndex = new SourceLineIndex(esTester.client()); + componentDao = new ComponentDao(); + DbClient dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), componentDao); + session = dbClient.openSession(false); + wsTester = new WsTester(new SourcesWs(new LinesAction(dbClient, sourceLineIndex, htmlSourceDecorator))); + + MockUserSession.set(); + } + + @After + public void tearDown() throws Exception { + session.close(); } @Test public void show_source() throws Exception { - String projectUuid = "abcd"; - String componentUuid = "efgh"; + newFile(); + Date updatedAt = new Date(); String scmDate = "2014-01-01T12:34:56.789Z"; SourceLineDoc line1 = new SourceLineDoc() - .setProjectUuid(projectUuid) - .setFileUuid(componentUuid) + .setProjectUuid(PROJECT_UUID) + .setFileUuid(FILE_UUID) .setLine(1) .setScmRevision("cafebabe") .setScmAuthor("polop") - .setSource("class Polop {") - .setHighlighting("h1") - .setSymbols("palap") + .setSource("package org.polop;") + .setHighlighting("0,7,k") + .setSymbols("8,17,42") .setUtLineHits(3) .setUtConditions(2) .setUtCoveredConditions(1) @@ -102,33 +117,33 @@ public class LinesActionTest { line1.setField(SourceLineIndexDefinition.FIELD_SCM_DATE, scmDate); SourceLineDoc line2 = new SourceLineDoc() - .setProjectUuid(projectUuid) - .setFileUuid(componentUuid) + .setProjectUuid(PROJECT_UUID) + .setFileUuid(FILE_UUID) .setLine(2) .setScmRevision("cafebabe") .setScmAuthor("polop") - .setSource(" // Empty") - .setHighlighting("h2") - .setSymbols("pulup") + .setSource("abc") + .setHighlighting("0,5,c") + .setSymbols("") .setUtLineHits(3) .setUtConditions(2) .setUtCoveredConditions(1) .setItLineHits(null) .setItConditions(null) .setItCoveredConditions(null) - .setDuplications(ImmutableList.of(1)) + .setDuplications(ImmutableList.of(1)) .setUpdateDate(updatedAt); line2.setField(SourceLineIndexDefinition.FIELD_SCM_DATE, scmDate); SourceLineDoc line3 = new SourceLineDoc() - .setProjectUuid(projectUuid) - .setFileUuid(componentUuid) + .setProjectUuid(PROJECT_UUID) + .setFileUuid(FILE_UUID) .setLine(3) .setScmRevision("cafebabe") .setScmAuthor("polop") .setSource("}") - .setHighlighting("h3") - .setSymbols("pylyp") + .setHighlighting(null) + .setSymbols(null) .setUtLineHits(null) .setUtConditions(null) .setUtCoveredConditions(null) @@ -139,33 +154,22 @@ public class LinesActionTest { .setUpdateDate(updatedAt); line3.setField(SourceLineIndexDefinition.FIELD_SCM_DATE, scmDate); - when(sourceLineIndex.getLines(eq(componentUuid), anyInt(), anyInt())).thenReturn(newArrayList( - line1, - line2, - line3 - )); + esTester.putDocuments(SourceLineIndexDefinition.INDEX, SourceLineIndexDefinition.TYPE, line1, line2, line3); - String componentKey = "componentKey"; - when(componentService.getByUuid(componentUuid)).thenReturn(new ComponentDto().setKey(componentKey).setProjectUuid(projectUuid)); - MockUserSession.set().setLogin("login").addProjectUuidPermissions(UserRole.CODEVIEWER, projectUuid); + MockUserSession.set().setLogin("login").addProjectUuidPermissions(UserRole.CODEVIEWER, PROJECT_UUID); - WsTester.TestRequest request = tester.newGetRequest("api/sources", "lines").setParam("uuid", componentUuid); - // Using non-strict match b/c of dates + WsTester.TestRequest request = wsTester.newGetRequest("api/sources", "lines").setParam("uuid", FILE_UUID); request.execute().assertJson(getClass(), "show_source.json"); } @Test public void fail_to_show_source_if_no_source_found() throws Exception { - String componentUuid = "abcd"; - String projectUuid = "efgh"; - when(sourceLineIndex.getLines(anyString(), anyInt(), anyInt())).thenReturn(Lists.newArrayList()); + newFile(); - String componentKey = "componentKey"; - when(componentService.getByUuid(componentUuid)).thenReturn(new ComponentDto().setKey(componentKey).setProjectUuid(projectUuid)); - MockUserSession.set().setLogin("login").addProjectUuidPermissions(UserRole.CODEVIEWER, projectUuid); + MockUserSession.set().setLogin("login").addProjectUuidPermissions(UserRole.CODEVIEWER, PROJECT_UUID); try { - WsTester.TestRequest request = tester.newGetRequest("api/sources", "lines").setParam("uuid", componentUuid); + WsTester.TestRequest request = wsTester.newGetRequest("api/sources", "lines").setParam("uuid", FILE_UUID); request.execute(); fail(); } catch (Exception e) { @@ -175,17 +179,14 @@ public class LinesActionTest { @Test public void show_source_with_from_and_to_params() throws Exception { - String projectUuid = "abcd"; - String fileUuid = "efgh"; + newFile(); - String componentKey = "componentKey"; - when(componentService.getByUuid(fileUuid)).thenReturn(new ComponentDto().setKey(componentKey).setProjectUuid(projectUuid)); - MockUserSession.set().setLogin("login").addProjectUuidPermissions(UserRole.CODEVIEWER, projectUuid); + MockUserSession.set().setLogin("login").addProjectUuidPermissions(UserRole.CODEVIEWER, PROJECT_UUID); - when(sourceLineIndex.getLines(fileUuid, 3, 3)).thenReturn(newArrayList( + esTester.putDocuments(SourceLineIndexDefinition.INDEX, SourceLineIndexDefinition.TYPE, new SourceLineDoc() - .setProjectUuid(projectUuid) - .setFileUuid(fileUuid) + .setProjectUuid(PROJECT_UUID) + .setFileUuid(FILE_UUID) .setLine(3) .setScmRevision("cafebabe") .setScmDate(null) @@ -201,25 +202,76 @@ public class LinesActionTest { .setItCoveredConditions(null) .setDuplications(null) .setUpdateDate(new Date()) - )); - WsTester.TestRequest request = tester + ); + + WsTester.TestRequest request = wsTester .newGetRequest("api/sources", "lines") - .setParam("uuid", fileUuid) + .setParam("uuid", FILE_UUID) .setParam("from", "3") .setParam("to", "3"); request.execute().assertJson(getClass(), "show_source_with_params_from_and_to.json"); } + @Test + public void show_source_by_file_key() throws Exception { + newFile(); + + esTester.putDocuments(SourceLineIndexDefinition.INDEX, SourceLineIndexDefinition.TYPE, + new SourceLineDoc() + .setProjectUuid(PROJECT_UUID) + .setFileUuid(FILE_UUID) + .setLine(3) + .setScmRevision("cafebabe") + .setScmDate(null) + .setScmAuthor("polop") + .setSource("}") + .setHighlighting("") + .setSymbols("") + .setUtLineHits(null) + .setUtConditions(null) + .setUtCoveredConditions(null) + .setItLineHits(null) + .setItConditions(null) + .setItCoveredConditions(null) + .setDuplications(null) + .setUpdateDate(new Date()) + ); + + MockUserSession.set().setLogin("login").addProjectUuidPermissions(UserRole.CODEVIEWER, PROJECT_UUID); + + WsTester.TestRequest request = wsTester.newGetRequest("api/sources", "lines").setParam("key", FILE_KEY); + request.execute().assertJson(getClass(), "show_source_by_file_key.json"); + } + + @Test + public void fail_when_no_uuid_or_key_param() throws Exception { + newFile(); + MockUserSession.set().setLogin("login").addProjectUuidPermissions(UserRole.CODEVIEWER, PROJECT_UUID); + WsTester.TestRequest request = wsTester.newGetRequest("api/sources", "lines"); + + try { + request.execute(); + failBecauseExceptionWasNotThrown(IllegalArgumentException.class); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Param uuid or param key is missing"); + } + } + @Test(expected = ForbiddenException.class) public void should_check_permission() throws Exception { - String fileUuid = "efgh"; + newFile(); - String componentKey = "componentKey"; - when(componentService.getByUuid(fileUuid)).thenReturn(new ComponentDto().setKey(componentKey)); MockUserSession.set().setLogin("login"); - tester.newGetRequest("api/sources", "lines") - .setParam("uuid", fileUuid) + wsTester.newGetRequest("api/sources", "lines") + .setParam("uuid", FILE_UUID) .execute(); } + + private void newFile(){ + ComponentDto project = ComponentTesting.newProjectDto(PROJECT_UUID); + ComponentDto file = ComponentTesting.newFileDto(project, FILE_UUID).setKey(FILE_KEY); + componentDao.insert(session, project, file); + session.commit(); + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesWsTest.java index 491d42912e6..32a7887d33c 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesWsTest.java @@ -22,7 +22,6 @@ package org.sonar.server.source.ws; import org.junit.Test; import org.sonar.api.server.ws.WebService; -import org.sonar.server.component.ComponentService; import org.sonar.server.db.DbClient; import org.sonar.server.source.HtmlSourceDecorator; import org.sonar.server.source.SourceService; @@ -36,7 +35,7 @@ public class SourcesWsTest { ShowAction showAction = new ShowAction(mock(SourceService.class), mock(DbClient.class)); RawAction rawAction = new RawAction(mock(DbClient.class), mock(SourceService.class)); - LinesAction linesAction = new LinesAction(mock(SourceLineIndex.class), mock(HtmlSourceDecorator.class), mock(ComponentService.class)); + LinesAction linesAction = new LinesAction(mock(DbClient.class), mock(SourceLineIndex.class), mock(HtmlSourceDecorator.class)); HashAction hashAction = new HashAction(mock(DbClient.class)); IndexAction indexAction = new IndexAction(mock(DbClient.class), mock(SourceService.class)); WsTester tester = new WsTester(new SourcesWs(showAction, rawAction, linesAction, hashAction, indexAction)); @@ -71,7 +70,7 @@ public class SourcesWsTest { assertThat(lines.since()).isEqualTo("5.0"); assertThat(lines.isInternal()).isTrue(); assertThat(lines.responseExampleAsString()).isNotEmpty(); - assertThat(lines.params()).hasSize(3); + assertThat(lines.params()).hasSize(4); WebService.Action hash = controller.action("hash"); assertThat(hash).isNotNull(); diff --git a/server/sonar-server/src/test/resources/org/sonar/server/source/ws/LinesActionTest/show_source.json b/server/sonar-server/src/test/resources/org/sonar/server/source/ws/LinesActionTest/show_source.json index 7655e20c817..eebd43bc502 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/source/ws/LinesActionTest/show_source.json +++ b/server/sonar-server/src/test/resources/org/sonar/server/source/ws/LinesActionTest/show_source.json @@ -1,7 +1,7 @@ { "sources": [ { - "code": "class Polop {", + "code": "package org.polop;", "line": 1, "scmAuthor": "polop", "scmRevision": "cafebabe", @@ -13,7 +13,7 @@ "itCoveredConditions": 1 }, { - "code": " // Empty", + "code": "abc", "line": 2, "scmAuthor": "polop", "scmRevision": "cafebabe", @@ -23,7 +23,7 @@ "duplicated": true }, { - "code": "}", + "code": "}", "line": 3, "scmAuthor": "polop", "scmRevision": "cafebabe", diff --git a/server/sonar-server/src/test/resources/org/sonar/server/source/ws/LinesActionTest/show_source_by_file_key.json b/server/sonar-server/src/test/resources/org/sonar/server/source/ws/LinesActionTest/show_source_by_file_key.json new file mode 100644 index 00000000000..ba2a4c6e52b --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/source/ws/LinesActionTest/show_source_by_file_key.json @@ -0,0 +1,10 @@ +{ + "sources": [ + { + "code": "}", + "line": 3, + "scmAuthor": "polop", + "scmRevision": "cafebabe" + } + ] +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/source/ws/LinesActionTest/show_source_with_params_from_and_to.json b/server/sonar-server/src/test/resources/org/sonar/server/source/ws/LinesActionTest/show_source_with_params_from_and_to.json index 8845334e1d9..ba2a4c6e52b 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/source/ws/LinesActionTest/show_source_with_params_from_and_to.json +++ b/server/sonar-server/src/test/resources/org/sonar/server/source/ws/LinesActionTest/show_source_with_params_from_and_to.json @@ -1,7 +1,7 @@ { "sources": [ { - "code": "}", + "code": "}", "line": 3, "scmAuthor": "polop", "scmRevision": "cafebabe"