blob: 08e98a45d1e78dc760b03a13f9685e9ea1f4209c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.servicecomb.transport.rest.vertx;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response.Status;
import org.apache.http.HttpHeaders;
import org.apache.servicecomb.common.rest.RestConst;
import org.apache.servicecomb.common.rest.RestProducerInvocation;
import org.apache.servicecomb.common.rest.VertxRestInvocation;
import org.apache.servicecomb.common.rest.filter.HttpServerFilter;
import org.apache.servicecomb.config.ConfigUtil;
import org.apache.servicecomb.core.SCBEngine;
import org.apache.servicecomb.core.Transport;
import org.apache.servicecomb.core.bootstrap.SCBBootstrap;
import org.apache.servicecomb.core.transport.TransportManager;
import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils;
import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx;
import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx;
import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
import io.vertx.codegen.annotations.Nullable;
import io.vertx.core.AsyncResult;
import io.vertx.core.Context;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.Cookie;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.SocketAddress;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import mockit.Deencapsulation;
import mockit.Expectations;
import mockit.Mock;
import mockit.MockUp;
import mockit.Mocked;
import org.junit.jupiter.api.Assertions;
public class TestVertxRestDispatcher {
@Mocked
Router mainRouter;
@Mocked
TransportManager transportManager;
VertxRestDispatcher dispatcher;
Throwable throwable;
boolean invoked;
@Before
public void setUp() {
ConfigUtil.installDynamicConfig();
dispatcher = new VertxRestDispatcher();
dispatcher.init(mainRouter);
new MockUp<RestProducerInvocation>() {
@Mock
void sendFailResponse(Throwable throwable) {
TestVertxRestDispatcher.this.throwable = throwable;
}
@Mock
void invoke(Transport transport, HttpServletRequestEx requestEx, HttpServletResponseEx responseEx,
List<HttpServerFilter> httpServerFilters) {
invoked = true;
}
};
SCBBootstrap.createSCBEngineForTest().setTransportManager(transportManager);
}
@After
public void teardown() {
SCBEngine.getInstance().destroy();
ArchaiusUtils.resetConfig();
}
@Test
public void getOrder() {
Assertions.assertEquals(Integer.MAX_VALUE, dispatcher.getOrder());
}
@Test
public void failureHandlerNormal(@Mocked RoutingContext context) {
RestProducerInvocation restProducerInvocation = new RestProducerInvocation();
Exception e = new Exception();
MockHttpServerResponse response = new MockHttpServerResponse();
new Expectations() {
{
context.get(RestConst.REST_PRODUCER_INVOCATION);
result = restProducerInvocation;
context.failure();
returns(e, e);
context.response();
result = response;
}
};
Deencapsulation.invoke(dispatcher, "failureHandler", context);
Assertions.assertSame(e, this.throwable);
Assertions.assertTrue(response.responseClosed);
}
@Test
public void failureHandlerErrorDataWithInvocation(@Mocked RoutingContext context, @Mocked InvocationException e) {
RestProducerInvocation restProducerInvocation = new RestProducerInvocation();
ErrorDataDecoderException edde = new ErrorDataDecoderException(e);
MockHttpServerResponse response = new MockHttpServerResponse();
new Expectations() {
{
context.get(RestConst.REST_PRODUCER_INVOCATION);
result = restProducerInvocation;
context.failure();
returns(edde, edde);
context.response();
result = response;
}
};
Deencapsulation.invoke(dispatcher, "failureHandler", context);
Assertions.assertSame(e, this.throwable);
Assertions.assertTrue(response.responseClosed);
}
@Test
public void failureHandlerErrorDataWithNormal(@Mocked RoutingContext context) {
RestProducerInvocation restProducerInvocation = new RestProducerInvocation();
Exception e = new Exception();
ErrorDataDecoderException edde = new ErrorDataDecoderException(e);
MockHttpServerResponse response = new MockHttpServerResponse();
new Expectations() {
{
context.get(RestConst.REST_PRODUCER_INVOCATION);
result = restProducerInvocation;
context.failure();
returns(edde, edde);
context.response();
result = response;
}
};
Deencapsulation.invoke(dispatcher, "failureHandler", context);
Assertions.assertSame(edde, this.throwable);
Assertions.assertTrue(response.responseClosed);
}
@Test
public void failureHandlerWithNoRestProducerInvocationAndInvocationException(@Mocked RoutingContext context) {
InvocationException e = new InvocationException(Status.REQUEST_ENTITY_TOO_LARGE, "testMsg");
ErrorDataDecoderException edde = new ErrorDataDecoderException(e);
MockHttpServerResponse response = new MockHttpServerResponse();
new Expectations() {
{
context.get(RestConst.REST_PRODUCER_INVOCATION);
result = null;
context.failure();
returns(edde, edde);
context.response();
result = response;
}
};
Deencapsulation.invoke(dispatcher, "failureHandler", context);
MatcherAssert.assertThat(response.responseHeader, Matchers.hasEntry(HttpHeaders.CONTENT_TYPE, MediaType.WILDCARD));
MatcherAssert.assertThat(response.responseStatusCode, Matchers.is(Status.REQUEST_ENTITY_TOO_LARGE.getStatusCode()));
MatcherAssert.assertThat(response.responseStatusMessage, Matchers.is(Status.REQUEST_ENTITY_TOO_LARGE.getReasonPhrase()));
MatcherAssert.assertThat(response.responseChunk,
Matchers.is("{\"message\":\"" + Status.REQUEST_ENTITY_TOO_LARGE.getReasonPhrase() + "\"}"));
Assertions.assertTrue(response.responseEnded);
}
@Test
public void failureHandlerWithNoRestProducerInvocationAndOtherException(@Mocked RoutingContext context) {
String exceptionMessage = "Internal Server Error";
Exception exception = new Exception(exceptionMessage);
MockHttpServerResponse response = new MockHttpServerResponse();
new Expectations() {
{
context.get(RestConst.REST_PRODUCER_INVOCATION);
result = null;
context.failure();
returns(exception, exception);
context.response();
result = response;
}
};
Deencapsulation.invoke(dispatcher, "failureHandler", context);
MatcherAssert.assertThat(response.responseHeader, Matchers.hasEntry(HttpHeaders.CONTENT_TYPE, MediaType.WILDCARD));
MatcherAssert.assertThat(response.responseStatusCode, Matchers.is(Status.INTERNAL_SERVER_ERROR.getStatusCode()));
MatcherAssert.assertThat(response.responseChunk,
Matchers.is("{\"message\":\"" + exceptionMessage + "\"}"));
Assertions.assertTrue(response.responseEnded);
}
@Test
public void failureHandlerWithNoExceptionAndStatusCodeIsSet(@Mocked RoutingContext context) {
MockHttpServerResponse response = new MockHttpServerResponse();
new Expectations() {
{
context.get(RestConst.REST_PRODUCER_INVOCATION);
result = null;
context.failure();
returns(null, null);
context.response();
result = response;
context.statusCode();
result = Status.REQUEST_ENTITY_TOO_LARGE.getStatusCode();
}
};
Deencapsulation.invoke(dispatcher, "failureHandler", context);
MatcherAssert.assertThat(response.responseHeader, Matchers.hasEntry(HttpHeaders.CONTENT_TYPE, MediaType.WILDCARD));
MatcherAssert.assertThat(response.responseStatusCode, Matchers.is(Status.REQUEST_ENTITY_TOO_LARGE.getStatusCode()));
Assertions.assertTrue(response.responseEnded);
}
@Test
public void failureHandlerWithNoExceptionAndStatusCodeIsNotSet(@Mocked RoutingContext context) {
MockHttpServerResponse response = new MockHttpServerResponse();
new Expectations() {
{
context.get(RestConst.REST_PRODUCER_INVOCATION);
result = null;
context.failure();
returns(null, null);
context.response();
result = response;
context.statusCode();
result = Status.OK.getStatusCode();
}
};
Deencapsulation.invoke(dispatcher, "failureHandler", context);
MatcherAssert.assertThat(response.responseHeader, Matchers.hasEntry(HttpHeaders.CONTENT_TYPE, MediaType.WILDCARD));
MatcherAssert.assertThat(response.responseStatusCode, Matchers.is(Status.INTERNAL_SERVER_ERROR.getStatusCode()));
MatcherAssert.assertThat(response.responseStatusMessage, Matchers.is(Status.INTERNAL_SERVER_ERROR.getReasonPhrase()));
MatcherAssert.assertThat(response.responseChunk,
Matchers.is("{\"message\":\"" + Status.INTERNAL_SERVER_ERROR.getReasonPhrase() + "\"}"));
Assertions.assertTrue(response.responseEnded);
}
@Test
public void onRequest(@Mocked Vertx vertx, @Mocked Context context, @Mocked HttpServerRequest request,
@Mocked SocketAddress socketAdrress) {
Map<String, Object> map = new HashMap<>();
RoutingContext routingContext = new MockUp<RoutingContext>() {
@Mock
RoutingContext put(String key, Object obj) {
map.put(key, obj);
return null;
}
@Mock
HttpServerRequest request() {
return request;
}
}.getMockInstance();
new Expectations() {
{
Vertx.currentContext();
result = context;
}
};
Deencapsulation.invoke(dispatcher, "onRequest", routingContext);
Assertions.assertEquals(VertxRestInvocation.class, map.get(RestConst.REST_PRODUCER_INVOCATION).getClass());
Assertions.assertTrue(invoked);
}
@Test
public void testWrapResponseBody() {
VertxRestDispatcher vertxRestDispatcher = new VertxRestDispatcher();
String message = "abcd";
String bodyString = vertxRestDispatcher.wrapResponseBody(message);
Assertions.assertNotNull(bodyString);
Assertions.assertEquals("{\"message\":\"abcd\"}", bodyString);
message = "\"abcd\"";
bodyString = vertxRestDispatcher.wrapResponseBody(message);
Assertions.assertNotNull(bodyString);
Assertions.assertEquals("{\"message\":\"\\\"abcd\\\"\"}", bodyString);
message = ".01ab\"!@#$%^&*()'\\cd";
bodyString = vertxRestDispatcher.wrapResponseBody(message);
Assertions.assertNotNull(bodyString);
Assertions.assertEquals("{\"message\":\".01ab\\\"!@#$%^&*()'\\\\cd\"}", bodyString);
message = new JsonObject().put("key", new JsonObject().put("k2", "value")).toString();
bodyString = vertxRestDispatcher.wrapResponseBody(message);
Assertions.assertNotNull(bodyString);
Assertions.assertEquals("{\"key\":{\"k2\":\"value\"}}", bodyString);
message = "ab\"23\n@!#cd";
bodyString = vertxRestDispatcher.wrapResponseBody(message);
Assertions.assertNotNull(bodyString);
Assertions.assertEquals("{\"message\":\"ab\\\"23\\n@!#cd\"}", bodyString);
message = "ab\"23\r\n@!#cd";
bodyString = vertxRestDispatcher.wrapResponseBody(message);
Assertions.assertNotNull(bodyString);
Assertions.assertEquals("{\"message\":\"ab\\\"23\\r\\n@!#cd\"}", bodyString);
}
}
class MockHttpServerResponse implements HttpServerResponse {
boolean responseClosed;
boolean responseEnded;
Map<String, String> responseHeader = new HashMap<>(1);
int responseStatusCode;
String responseStatusMessage;
String responseChunk;
@Override
public void close() {
responseClosed = true;
}
@Override
public HttpServerResponse putHeader(String name, String value) {
responseHeader.put(name, value);
return this;
}
@Override
public HttpServerResponse setStatusCode(int statusCode) {
responseStatusCode = statusCode;
return this;
}
@Override
public HttpServerResponse setStatusMessage(String statusMessage) {
responseStatusMessage = statusMessage;
return this;
}
@Override
public Future<Void> end() {
responseEnded = true;
return Future.succeededFuture();
}
@Override
public void end(Handler<AsyncResult<Void>> handler) {
}
@Override
public Future<Void> end(String chunk) {
responseEnded = true;
responseChunk = chunk;
return Future.succeededFuture();
}
@Override
public void end(String s, Handler<AsyncResult<Void>> handler) {
}
@Override
public HttpServerResponse exceptionHandler(Handler<Throwable> handler) {
return null;
}
@Override
public Future<Void> write(Buffer data) {
return Future.succeededFuture();
}
@Override
public void write(Buffer buffer, Handler<AsyncResult<Void>> handler) {
}
@Override
public HttpServerResponse setWriteQueueMaxSize(int maxSize) {
return null;
}
@Override
public boolean writeQueueFull() {
return false;
}
@Override
public HttpServerResponse drainHandler(Handler<Void> handler) {
return null;
}
@Override
public int getStatusCode() {
return 0;
}
@Override
public String getStatusMessage() {
return null;
}
@Override
public HttpServerResponse setChunked(boolean chunked) {
return null;
}
@Override
public boolean isChunked() {
return false;
}
@Override
public MultiMap headers() {
return null;
}
@Override
public HttpServerResponse putHeader(CharSequence name, CharSequence value) {
return null;
}
@Override
public HttpServerResponse putHeader(String name, Iterable<String> values) {
return null;
}
@Override
public HttpServerResponse putHeader(CharSequence name, Iterable<CharSequence> values) {
return null;
}
@Override
public MultiMap trailers() {
return null;
}
@Override
public HttpServerResponse putTrailer(String name, String value) {
return null;
}
@Override
public HttpServerResponse putTrailer(CharSequence name, CharSequence value) {
return null;
}
@Override
public HttpServerResponse putTrailer(String name, Iterable<String> values) {
return null;
}
@Override
public HttpServerResponse putTrailer(CharSequence name, Iterable<CharSequence> value) {
return null;
}
@Override
public HttpServerResponse closeHandler(Handler<Void> handler) {
return null;
}
@Override
public HttpServerResponse endHandler(Handler<Void> handler) {
return null;
}
@Override
public Future<Void> write(String chunk, String enc) {
return Future.succeededFuture();
}
@Override
public void write(String s, String s1, Handler<AsyncResult<Void>> handler) {
}
@Override
public Future<Void> write(String chunk) {
return Future.succeededFuture();
}
@Override
public void write(String s, Handler<AsyncResult<Void>> handler) {
}
@Override
public HttpServerResponse writeContinue() {
return null;
}
@Override
public Future<Void> end(String chunk, String enc) {
return Future.succeededFuture();
}
@Override
public void end(String s, String s1, Handler<AsyncResult<Void>> handler) {
}
@Override
public Future<Void> end(Buffer chunk) {
return Future.succeededFuture();
}
@Override
public void end(Buffer buffer, Handler<AsyncResult<Void>> handler) {
}
@Override
public Future<Void> sendFile(String filename, long offset, long length) {
return Future.succeededFuture();
}
@Override
public HttpServerResponse sendFile(String filename, long offset, long length,
Handler<AsyncResult<Void>> resultHandler) {
return null;
}
@Override
public boolean ended() {
return false;
}
@Override
public boolean closed() {
return false;
}
@Override
public boolean headWritten() {
return false;
}
@Override
public HttpServerResponse headersEndHandler(Handler<Void> handler) {
return null;
}
@Override
public HttpServerResponse bodyEndHandler(Handler<Void> handler) {
return null;
}
@Override
public long bytesWritten() {
return 0;
}
@Override
public int streamId() {
return 0;
}
@Override
public HttpServerResponse push(HttpMethod method, String host, String path,
Handler<AsyncResult<HttpServerResponse>> handler) {
return null;
}
@Override
public HttpServerResponse push(HttpMethod method, String path, MultiMap headers,
Handler<AsyncResult<HttpServerResponse>> handler) {
return null;
}
@Override
public HttpServerResponse push(HttpMethod method, String path, Handler<AsyncResult<HttpServerResponse>> handler) {
return null;
}
@Override
public HttpServerResponse push(HttpMethod method, String host, String path, MultiMap headers,
Handler<AsyncResult<HttpServerResponse>> handler) {
return null;
}
@Override
public Future<HttpServerResponse> push(HttpMethod method, String host, String path, MultiMap headers) {
return Future.succeededFuture();
}
@Override
public boolean reset(long code) {
return false;
}
@Override
public HttpServerResponse writeCustomFrame(int type, int flags, Buffer payload) {
return null;
}
@Override
public HttpServerResponse addCookie(Cookie cookie) {
return null;
}
@Override
public @Nullable Cookie removeCookie(String name, boolean invalidate) {
return null;
}
@Override
public Set<Cookie> removeCookies(String name, boolean invalidate) {
return null;
}
@Override
public @Nullable Cookie removeCookie(String name, String domain, String path, boolean invalidate) {
return null;
}
}