<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
- xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
- id="SonarQube"
- version="3.0"
- metadata-complete="true">
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ id="SonarQube"
+ version="3.0"
+ metadata-complete="true">
<display-name>SonarQube</display-name>
<filter>
<filter-name>ServletFilters</filter-name>
<filter-class>org.sonar.server.platform.web.MasterServletFilter</filter-class>
+ <async-supported>true</async-supported>
</filter>
<filter>
<filter-name>UserSessionFilter</filter-name>
<filter-class>org.sonar.server.platform.web.UserSessionFilter</filter-class>
+ <async-supported>true</async-supported>
</filter>
<filter>
<filter-name>SetCharacterEncodingFilter</filter-name>
<filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class>
+ <async-supported>true</async-supported>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
<filter>
<filter-name>SecurityFilter</filter-name>
<filter-class>org.sonar.server.platform.web.SecurityServletFilter</filter-class>
+ <async-supported>true</async-supported>
</filter>
<filter>
<filter-name>RootFilter</filter-name>
<filter-class>org.sonar.server.platform.web.RootFilter</filter-class>
+ <async-supported>true</async-supported>
</filter>
<filter>
<filter-name>RedirectFilter</filter-name>
<filter-class>org.sonar.server.platform.web.RedirectFilter</filter-class>
+ <async-supported>true</async-supported>
</filter>
<filter>
<filter-name>RequestUidFilter</filter-name>
<filter-class>org.sonar.server.platform.web.RequestIdFilter</filter-class>
+ <async-supported>true</async-supported>
</filter>
<filter>
<filter-name>WebPagesFilter</filter-name>
<filter-class>org.sonar.server.platform.web.WebPagesFilter</filter-class>
+ <async-supported>true</async-supported>
</filter>
<filter>
<filter-name>CacheControlFilter</filter-name>
<filter-class>org.sonar.server.platform.web.CacheControlFilter</filter-class>
+ <async-supported>true</async-supported>
</filter>
<!-- order of execution is important -->
<url-pattern>/*</url-pattern>
</filter-mapping>
+ <servlet>
+ <servlet-name>default-servlet</servlet-name>
+ <servlet-class>
+ org.apache.catalina.servlets.DefaultServlet
+ </servlet-class>
+ <load-on-startup>1</load-on-startup>
+ <async-supported>true</async-supported>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>default</servlet-name>
+ <url-pattern>/</url-pattern>
+ </servlet-mapping>
+
<servlet>
<servlet-name>static</servlet-name>
<servlet-class>org.sonar.server.platform.web.StaticResourcesServlet</servlet-class>
}
dependencies {
+ compile 'javax.servlet:javax.servlet-api'
compile project(':server:sonar-webserver-auth')
compile project(':server:sonar-webserver-ws')
testCompile 'junit:junit'
testCompile 'org.assertj:assertj-core'
testCompile 'org.mockito:mockito-core'
-
testCompile testFixtures(project(':server:sonar-webserver-ws'))
+
+ testFixturesApi project(':sonar-testing-harness')
+ testFixturesCompileOnly testFixtures(project(':server:sonar-webserver-ws'))
}
*/
package org.sonar.server.pushapi;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import javax.servlet.http.HttpServletResponse;
+import org.sonar.server.ws.ServletRequest;
+import org.sonar.server.ws.ServletResponse;
import org.sonar.server.ws.WsAction;
-public interface ServerPushAction extends WsAction {
- //marker interface
+public abstract class ServerPushAction implements WsAction {
+
+ protected boolean isServerSideEventsRequest(ServletRequest request) {
+ Map<String, String> headers = request.getHeaders();
+ String accept = headers.get("accept");
+ if (accept != null) {
+ return accept.contains("text/event-stream");
+ }
+ return false;
+ }
+
+ protected void setHeadersForResponse(ServletResponse response) throws IOException {
+ response.stream().setStatus(HttpServletResponse.SC_OK);
+ response.stream().setCharacterEncoding(StandardCharsets.UTF_8.name());
+ response.stream().setMediaType("text/event-stream");
+ // By adding this header, and not closing the connection,
+ // we disable HTTP chunking, and we can use write()+flush()
+ // to send data in the text/event-stream protocol
+ response.setHeader("Connection", "close");
+ response.stream().flushBuffer();
+ }
+
}
*/
package org.sonar.server.pushapi.sonarlint;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import javax.servlet.AsyncContext;
+import javax.servlet.http.HttpServletResponse;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.server.pushapi.ServerPushAction;
+import org.sonar.server.ws.ServletRequest;
+import org.sonar.server.ws.ServletResponse;
-public class SonarLintPushAction implements ServerPushAction {
+public class SonarLintPushAction extends ServerPushAction {
private static final Logger LOGGER = Loggers.get(SonarLintPushAction.class);
}
@Override
- public void handle(Request request, Response response) {
+ public void handle(Request request, Response response) throws IOException {
+ ServletRequest servletRequest = (ServletRequest) request;
+ ServletResponse servletResponse = (ServletResponse) response;
+
String projectKeys = request.getParam(PROJECT_PARAM_KEY).getValue();
String languages = request.getParam(LANGUAGE_PARAM_KEY).getValue();
- //to remove later
+ // to remove later
LOGGER.debug(projectKeys != null ? projectKeys : "");
LOGGER.debug(languages != null ? languages : "");
- response.noContent();
+ AsyncContext asyncContext = servletRequest.startAsync();
+ asyncContext.setTimeout(0);
+
+ if (!isServerSideEventsRequest(servletRequest)) {
+ servletResponse.stream().setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
+ return;
+ }
+
+ setHeadersForResponse(servletResponse);
+
+ //test response to remove later
+ response.stream().output().write("Hello world".getBytes(StandardCharsets.UTF_8));
+ response.stream().output().flush();
}
}
assertThat(controller.actions()).isNotEmpty();
}
- private static class DummyServerPushAction implements ServerPushAction {
+ private static class DummyServerPushAction extends ServerPushAction {
@Override
public void define(WebService.NewController context) {
import org.junit.Test;
import org.sonar.api.server.ws.WebService;
-import org.sonar.server.ws.TestRequest;
+import org.sonar.server.pushapi.TestPushRequest;
+import org.sonar.server.pushapi.WsPushActionTester;
import org.sonar.server.ws.TestResponse;
-import org.sonar.server.ws.WsActionTester;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class SonarLintPushActionTest {
- private final WsActionTester ws = new WsActionTester(new SonarLintPushAction());
+ private final WsPushActionTester ws = new WsPushActionTester(new SonarLintPushAction());
@Test
public void defineTest() {
}
@Test
- public void handle_returnsNoResponseWhenParamsProvided() {
- TestResponse response = ws.newRequest()
+ public void handle_returnsNoResponseWhenParamsAndHeadersProvided() {
+ TestResponse response = ws.newPushRequest()
.setParam("projectKeys", "project1,project2")
.setParam("languages", "java")
+ .setHeader("accept", "text/event-stream")
.execute();
- assertThat(response.getStatus()).isEqualTo(204);
+ assertThat(response.getInput()).isEqualTo("Hello world");
+ }
+
+ @Test
+ public void handle_whenAcceptHeaderNotProvided_statusCode406() {
+ TestResponse testResponse = ws.newPushRequest().
+ setParam("projectKeys", "project1,project2")
+ .setParam("languages", "java")
+ .execute();
+
+ assertThat(testResponse.getStatus()).isEqualTo(406);
}
@Test
public void handle_whenParamsNotProvided_throwException() {
- TestRequest testRequest = ws.newRequest();
+ TestPushRequest testRequest = ws.newPushRequest()
+ .setHeader("accept", "text/event-stream");
+
assertThatThrownBy(testRequest::execute)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("The 'projectKeys' parameter is missing");
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.pushapi;
+
+import com.google.common.base.Throwables;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.net.HttpURLConnection;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.io.IOUtils;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.api.utils.text.XmlWriter;
+import org.sonar.server.ws.ServletResponse;
+import org.sonar.server.ws.TestableResponse;
+
+import static org.mockito.Mockito.mock;
+
+public class DumbPushResponse extends ServletResponse implements TestableResponse {
+
+ public DumbPushResponse() {
+ super(mock(HttpServletResponse.class));
+ }
+
+ private DumbPushResponse.InMemoryStream stream;
+
+ private final ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+ private Map<String, String> headers = new HashMap<>();
+
+ public class InMemoryStream extends ServletStream {
+ private String mediaType;
+
+ private int status = 200;
+
+ public InMemoryStream() {
+ super(mock(HttpServletResponse.class));
+ }
+
+ @Override
+ public ServletStream setMediaType(String s) {
+ this.mediaType = s;
+ return this;
+ }
+
+ @Override
+ public ServletStream setStatus(int i) {
+ this.status = i;
+ return this;
+ }
+
+ @Override
+ public OutputStream output() {
+ return output;
+ }
+ }
+
+ @Override
+ public JsonWriter newJsonWriter() {
+ return JsonWriter.of(new OutputStreamWriter(output, StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public XmlWriter newXmlWriter() {
+ return XmlWriter.of(new OutputStreamWriter(output, StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public ServletStream stream() {
+ if (stream == null) {
+ stream = new DumbPushResponse.InMemoryStream();
+ }
+ return stream;
+ }
+
+ @Override
+ public Response noContent() {
+ stream().setStatus(HttpURLConnection.HTTP_NO_CONTENT);
+ IOUtils.closeQuietly(output);
+ return this;
+ }
+
+ @CheckForNull
+ public String mediaType() {
+ return ((DumbPushResponse.InMemoryStream) stream()).mediaType;
+ }
+
+ public int status() {
+ return ((InMemoryStream) stream()).status;
+ }
+
+ @Override
+ public Response setHeader(String name, String value) {
+ headers.put(name, value);
+ return this;
+ }
+
+ public Collection<String> getHeaderNames() {
+ return headers.keySet();
+ }
+
+ @CheckForNull
+ public String getHeader(String name) {
+ return headers.get(name);
+ }
+
+ public byte[] getFlushedOutput() {
+ try {
+ output.flush();
+ return output.toByteArray();
+ } catch (IOException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+}
--- /dev/null
+package org.sonar.server.pushapi;/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.
+ */
+
+import com.google.common.base.Throwables;
+import java.util.Map;
+import java.util.Optional;
+import javax.servlet.AsyncContext;
+import org.sonar.server.ws.ServletRequest;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.TestResponse;
+
+import static org.mockito.Mockito.mock;
+
+public class TestPushRequest extends ServletRequest {
+
+ private TestRequest testRequest = new TestRequest();
+
+ public TestPushRequest() {
+ super(null);
+ }
+
+ @Override
+ public AsyncContext startAsync() {
+ return mock(AsyncContext.class);
+ }
+
+ @Override
+ public String method() {
+ return testRequest.method();
+ }
+
+ @Override
+ public boolean hasParam(String key) {
+ return testRequest.hasParam(key);
+ }
+
+ @Override
+ public Map<String, String[]> getParams() {
+ return testRequest.getParams();
+ }
+
+ @Override
+ public String readParam(String key) {
+ return testRequest.readParam(key);
+ }
+
+ @Override
+ public String getMediaType() {
+ return testRequest.getMediaType();
+ }
+
+ public TestPushRequest setParam(String key, String value) {
+ testRequest.setParam(key, value);
+ return this;
+ }
+
+ @Override
+ public Map<String, String> getHeaders() {
+ return testRequest.getHeaders();
+ }
+
+ @Override
+ public Optional<String> header(String name) {
+ return testRequest.header(name);
+ }
+
+ public TestPushRequest setHeader(String name, String value) {
+ testRequest.setHeader(name, value);
+ return this;
+ }
+
+ public TestResponse execute() {
+ try {
+ DumbPushResponse response = new DumbPushResponse();
+ action().handler().handle(this, response);
+ return new TestResponse(response);
+ } catch (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ }
+}
--- /dev/null
+package org.sonar.server.pushapi;/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.
+ */
+
+import org.sonar.server.ws.WsAction;
+import org.sonar.server.ws.WsActionTester;
+
+public class WsPushActionTester extends WsActionTester {
+ public WsPushActionTester(WsAction wsAction) {
+ super(wsAction);
+ }
+
+ public TestPushRequest newPushRequest() {
+ TestPushRequest request = new TestPushRequest();
+ request.setAction(action);
+ return request;
+ }
+}
compileOnly 'javax.servlet:javax.servlet-api'
compileOnly 'org.apache.tomcat.embed:tomcat-embed-core'
+ testCompile 'com.tngtech.java:junit-dataprovider'
+ testCompile 'junit:junit'
testCompile 'com.google.code.findbugs:jsr305'
testCompile 'javax.servlet:javax.servlet-api'
testCompile 'org.apache.tomcat.embed:tomcat-embed-core'
import java.util.Map;
import java.util.Optional;
import javax.annotation.CheckForNull;
+import javax.servlet.AsyncContext;
import javax.servlet.http.HttpServletRequest;
import org.sonar.api.impl.ws.PartImpl;
import org.sonar.api.impl.ws.ValidatingRequest;
}
}
+ public AsyncContext startAsync() {
+ return source.startAsync();
+ }
+
private boolean isMultipartContent() {
String contentType = source.getContentType();
return contentType != null && contentType.toLowerCase(ENGLISH).startsWith(MULTIPART);
response.reset();
return this;
}
+
+ public ServletStream flushBuffer() throws IOException {
+ response.flushBuffer();
+ return this;
+ }
+
+ public ServletStream setCharacterEncoding(String charset) {
+ response.setCharacterEncoding(charset);
+ return this;
+ }
}
@Override
assertThat(underTest.getReader()).isEqualTo(reader);
}
+ @Test
+ public void startAsync() {
+ underTest.startAsync();
+
+ verify(source).startAsync();
+ }
+
}
*/
package org.sonar.server.ws;
+import java.io.IOException;
+import java.nio.charset.Charset;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
verify(response).setStatus(404);
}
+ @Test
+ public void setCharacterEncoding_encodingIsSet() {
+ underTest.stream().setCharacterEncoding("UTF-8");
+
+ verify(response).setCharacterEncoding("UTF-8");
+ }
+
+ @Test
+ public void flushBuffer_bufferIsFlushed() throws IOException {
+ underTest.stream().flushBuffer();
+
+ verify(response).flushBuffer();
+ }
+
@Test
public void test_output() {
assertThat(underTest.stream().output()).isEqualTo(output);
*/
package org.sonar.server.ws;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.util.function.Consumer;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.ClientAbortException;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.RequestHandler;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+@RunWith(DataProviderRunner.class)
public class WebServiceEngineTest {
@Rule
}
}
- @Test
- public void ws_returns_successful_response() {
- Request request = new TestRequest().setPath("/api/ping");
-
- DumbResponse response = run(request, newPingWs(a -> {
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("pong");
- assertThat(response.stream().status()).isEqualTo(200);
+ @DataProvider
+ public static Object[][] responseData() {
+ return new Object[][] {
+ {"/api/ping", "pong", 200},
+ {"api/ping", "pong", 200},
+ {"api/ping.json", "pong", 200},
+ {"xxx/ping", "{\"errors\":[{\"msg\":\"Unknown url : xxx/ping\"}]}", 404},
+ {"api/xxx", "{\"errors\":[{\"msg\":\"Unknown url : api/xxx\"}]}", 404}
+ };
}
@Test
- public void accept_path_that_does_not_start_with_slash() {
- Request request = new TestRequest().setPath("api/ping");
+ @UseDataProvider("responseData")
+ public void ws_returns_successful_response(String path, String output, int statusCode) {
+ Request request = new TestRequest().setPath(path);
DumbResponse response = run(request, newPingWs(a -> {
}));
- assertThat(response.stream().outputAsString()).isEqualTo("pong");
- assertThat(response.stream().status()).isEqualTo(200);
- }
-
- @Test
- public void request_path_can_contain_valid_media_type() {
- Request request = new TestRequest().setPath("api/ping.json");
-
- DumbResponse response = run(request, newPingWs(a -> {
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("pong");
- assertThat(response.stream().status()).isEqualTo(200);
+ assertThat(response.stream().outputAsString()).isEqualTo(output);
+ assertThat(response.status()).isEqualTo(statusCode);
}
@Test
DumbResponse response = run(request, newPingWs(a -> {
}));
- assertThat(response.stream().status()).isEqualTo(400);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
+ assertThat(response.status()).isEqualTo(400);
+ assertThat(response.mediaType()).isEqualTo(MediaTypes.JSON);
assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"Unknown action extension: bat\"}]}");
}
DumbResponse response = run(request, newWs("api/foo", a -> a.setHandler(handler)));
assertThat(response.stream().outputAsString()).isEmpty();
- assertThat(response.stream().status()).isEqualTo(204);
- }
-
- @Test
- public void return_404_if_controller_does_not_exist() {
- Request request = new TestRequest().setPath("xxx/ping");
-
- DumbResponse response = run(request, newPingWs(a -> {
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"Unknown url : xxx/ping\"}]}");
- assertThat(response.stream().status()).isEqualTo(404);
- }
-
- @Test
- public void return_404_if_action_does_not_exist() {
- Request request = new TestRequest().setPath("api/xxx");
-
- DumbResponse response = run(request, newPingWs(a -> {
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"Unknown url : api/xxx\"}]}");
- assertThat(response.stream().status()).isEqualTo(404);
+ assertThat(response.status()).isEqualTo(204);
}
@Test
DumbResponse response = run(request, newWs("api/foo", a -> a.setPost(true)));
assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"HTTP method POST is required\"}]}");
- assertThat(response.stream().status()).isEqualTo(405);
+ assertThat(response.status()).isEqualTo(405);
}
@Test
}));
assertThat(response.stream().outputAsString()).isEqualTo("pong");
- assertThat(response.stream().status()).isEqualTo(200);
+ assertThat(response.status()).isEqualTo(200);
}
@Test
}));
assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"HTTP method PUT is not allowed\"}]}");
- assertThat(response.stream().status()).isEqualTo(405);
+ assertThat(response.status()).isEqualTo(405);
}
@Test
}));
assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"HTTP method DELETE is not allowed\"}]}");
- assertThat(response.stream().status()).isEqualTo(405);
+ assertThat(response.status()).isEqualTo(405);
}
@Test
DumbResponse response = run(request, newPingWs(a -> a.setPost(true)));
assertThat(response.stream().outputAsString()).isEqualTo("pong");
- assertThat(response.stream().status()).isEqualTo(200);
+ assertThat(response.status()).isEqualTo(200);
}
@Test
DumbResponse response = run(request, newWs("api/foo", a -> a.setHandler((req, resp) -> request.param("unknown"))));
assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"BUG - parameter \\u0027unknown\\u0027 is undefined for action \\u0027foo\\u0027\"}]}");
- assertThat(response.stream().status()).isEqualTo(400);
+ assertThat(response.status()).isEqualTo(400);
}
@Test
}));
assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"The \\u0027bar\\u0027 parameter is missing\"}]}");
- assertThat(response.stream().status()).isEqualTo(400);
+ assertThat(response.status()).isEqualTo(400);
}
@Test
}));
assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"The \\u0027bar\\u0027 parameter is missing\"}]}");
- assertThat(response.stream().status()).isEqualTo(400);
+ assertThat(response.status()).isEqualTo(400);
}
@Test
}));
assertThat(response.stream().outputAsString()).isEqualTo("hello");
- assertThat(response.stream().status()).isEqualTo(200);
+ assertThat(response.status()).isEqualTo(200);
}
@Test
}));
assertThat(response.stream().outputAsString()).isEqualTo("bar");
- assertThat(response.stream().status()).isEqualTo(200);
+ assertThat(response.status()).isEqualTo(200);
}
@Test
}));
assertThat(response.stream().outputAsString()).isEqualTo("json");
- assertThat(response.stream().status()).isEqualTo(200);
+ assertThat(response.status()).isEqualTo(200);
}
@Test
}));
assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"Value of parameter \\u0027format\\u0027 (yml) must be one of: [json, xml]\"}]}");
- assertThat(response.stream().status()).isEqualTo(400);
+ assertThat(response.status()).isEqualTo(400);
}
@Test
DumbResponse response = run(request, newFailWs());
assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"An error has occurred. Please contact your administrator\"}]}");
- assertThat(response.stream().status()).isEqualTo(500);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
+ assertThat(response.status()).isEqualTo(500);
+ assertThat(response.mediaType()).isEqualTo(MediaTypes.JSON);
assertThat(logTester.logs(LoggerLevel.ERROR)).filteredOn(l -> l.contains("Fail to process request api/foo")).isNotEmpty();
}
assertThat(response.stream().outputAsString()).isEqualTo(
"{\"errors\":[{\"msg\":\"Bad request !\"}]}");
- assertThat(response.stream().status()).isEqualTo(400);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
+ assertThat(response.status()).isEqualTo(400);
+ assertThat(response.mediaType()).isEqualTo(MediaTypes.JSON);
assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
}
+ "{\"msg\":\"two\"},"
+ "{\"msg\":\"three\"}"
+ "]}");
- assertThat(response.stream().status()).isEqualTo(400);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
+ assertThat(response.status()).isEqualTo(400);
+ assertThat(response.mediaType()).isEqualTo(MediaTypes.JSON);
assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
}
assertThat(response.stream().outputAsString()).isEqualTo(
"{\"scope\":\"PROJECT\",\"errors\":[{\"msg\":\"Bad request !\"}]}");
- assertThat(response.stream().status()).isEqualTo(400);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
+ assertThat(response.status()).isEqualTo(400);
+ assertThat(response.mediaType()).isEqualTo(MediaTypes.JSON);
assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
}
})));
assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"this should not fail %s\"}]}");
- assertThat(response.stream().status()).isEqualTo(400);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
+ assertThat(response.status()).isEqualTo(400);
+ assertThat(response.mediaType()).isEqualTo(MediaTypes.JSON);
}
@Test
Issues.Issue msg = Issues.Issue.newBuilder().setKey("I1").build();
WsUtils.writeProtobuf(msg, request, response);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
+ assertThat(response.mediaType()).isEqualTo(MediaTypes.JSON);
assertThat(response.outputAsString())
.startsWith("{")
.contains("\"key\":\"I1\"")
Issues.Issue msg = Issues.Issue.newBuilder().setKey("I1").build();
WsUtils.writeProtobuf(msg, request, response);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.PROTOBUF);
+ assertThat(response.mediaType()).isEqualTo(MediaTypes.PROTOBUF);
assertThat(Issues.Issue.parseFrom(response.getFlushedOutput()).getKey()).isEqualTo("I1");
}
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.api.utils.text.XmlWriter;
-public class DumbResponse implements Response {
+public class DumbResponse implements Response, TestableResponse {
private InMemoryStream stream;
private final ByteArrayOutputStream output = new ByteArrayOutputStream();
private int status = 200;
- @CheckForNull
- public String mediaType() {
- return mediaType;
- }
-
- public int status() {
- return status;
- }
-
@Override
public Response.Stream setMediaType(String s) {
this.mediaType = s;
return new String(output.toByteArray(), StandardCharsets.UTF_8);
}
+ @CheckForNull
+ public String mediaType() {
+ return stream().mediaType;
+ }
+
+ public int status() {
+ return stream().status;
+ }
+
@Override
public Response setHeader(String name, String value) {
headers.put(name, value);
}
@Override
- protected String readParam(String key) {
+ public String readParam(String key) {
return params.get(key);
}
@Override
- protected List<String> readMultiParam(String key) {
+ public List<String> readMultiParam(String key) {
return multiParams.get(key);
}
@Override
- protected InputStream readInputStreamParam(String key) {
+ public InputStream readInputStreamParam(String key) {
String value = readParam(key);
if (value == null) {
return null;
}
@Override
- protected Part readPart(String key) {
+ public Part readPart(String key) {
return parts.get(key);
}
public class TestResponse {
- private final DumbResponse dumbResponse;
+ private final TestableResponse testableResponse;
- public TestResponse(DumbResponse dumbResponse) {
- this.dumbResponse = dumbResponse;
+ public TestResponse(TestableResponse dumbResponse) {
+ this.testableResponse = dumbResponse;
}
public InputStream getInputStream() {
- return new ByteArrayInputStream(dumbResponse.getFlushedOutput());
+ return new ByteArrayInputStream(testableResponse.getFlushedOutput());
}
public <T extends GeneratedMessageV3> T getInputObject(Class<T> protobufClass) {
}
public String getInput() {
- return new String(dumbResponse.getFlushedOutput(), StandardCharsets.UTF_8);
+ return new String(testableResponse.getFlushedOutput(), StandardCharsets.UTF_8);
}
public String getMediaType() {
- return dumbResponse.stream().mediaType();
+ return testableResponse.mediaType();
}
public int getStatus() {
- return dumbResponse.stream().status();
+ return testableResponse.status();
}
@CheckForNull
public String getHeader(String headerKey) {
- return dumbResponse.getHeader(headerKey);
+ return testableResponse.getHeader(headerKey);
}
public void assertJson(String expectedJson) {
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.ws;
+
+import org.sonar.api.server.ws.Response;
+
+public interface TestableResponse {
+
+ byte[] getFlushedOutput();
+
+ Response.Stream stream();
+
+ String getHeader(String headerKey);
+
+ int status();
+
+ String mediaType();
+}
public class WsActionTester {
public static final String CONTROLLER_KEY = "test";
- private final WebService.Action action;
+ protected final WebService.Action action;
public WsActionTester(WsAction wsAction) {
WebService.Context context = new WebService.Context();
appender.setEncoder(fileEncoder);
appender.start();
valve.addAppender(appender);
+ valve.setAsyncSupported(true);
tomcat.getHost().getPipeline().addValve(valve);
}
}
compile 'org.assertj:assertj-core'
compile 'org.hamcrest:hamcrest-core'
compile 'org.jsoup:jsoup'
+ compile 'org.mockito:mockito-core'
compileOnly 'com.google.code.findbugs:jsr305'
}