blob: ab574b91ca939daa69324b6a1899cc7400bbb57c [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.client.http;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.log4j.Level;
import org.apache.servicecomb.common.rest.RestConst;
import org.apache.servicecomb.common.rest.VertxRestInvocation;
import org.apache.servicecomb.common.rest.definition.RestOperationMeta;
import org.apache.servicecomb.common.rest.definition.path.URLPathBuilder;
import org.apache.servicecomb.common.rest.filter.HttpClientFilter;
import org.apache.servicecomb.core.Endpoint;
import org.apache.servicecomb.core.Invocation;
import org.apache.servicecomb.core.definition.OperationConfig;
import org.apache.servicecomb.core.definition.OperationMeta;
import org.apache.servicecomb.core.executor.ReactiveExecutor;
import org.apache.servicecomb.core.invocation.InvocationStageTrace;
import org.apache.servicecomb.core.tracing.TraceIdLogger;
import org.apache.servicecomb.foundation.common.net.URIEndpointObject;
import org.apache.servicecomb.foundation.common.utils.JsonUtils;
import org.apache.servicecomb.foundation.test.scaffolding.exception.RuntimeExceptionWithoutStackTrace;
import org.apache.servicecomb.foundation.test.scaffolding.log.LogCollector;
import org.apache.servicecomb.foundation.vertx.client.http.HttpClientWithContext;
import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx;
import org.apache.servicecomb.registry.definition.DefinitionConst;
import org.apache.servicecomb.swagger.invocation.AsyncResponse;
import org.apache.servicecomb.swagger.invocation.Response;
import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.vertx.core.Context;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpClientResponse;
import io.vertx.core.http.impl.headers.HeadersMultiMap;
import mockit.Deencapsulation;
import mockit.Expectations;
import mockit.Mock;
import mockit.MockUp;
import mockit.Mocked;
public class TestRestClientInvocation {
private static final String TARGET_MICROSERVICE_NAME = "TargetMS";
Handler<Buffer> bodyHandler;
MultiMap headers = new HeadersMultiMap();
HttpClientRequest request = mock(HttpClientRequest.class);
HttpClientResponse httpClientResponse = mock(HttpClientResponse.class);
HttpClient httpClient = mock(HttpClient.class);
Context context = mock(Context.class);
HttpClientWithContext httpClientWithContext = new HttpClientWithContext(httpClient, context);
Invocation invocation = mock(Invocation.class);
InvocationStageTrace invocationStageTrace = new InvocationStageTrace(invocation);
Response response;
AsyncResponse asyncResp = resp -> response = resp;
OperationMeta operationMeta = mock(OperationMeta.class);
Endpoint endpoint = mock(Endpoint.class);
RestOperationMeta swaggerRestOperation = mock(RestOperationMeta.class);
URLPathBuilder urlPathBuilder = mock(URLPathBuilder.class);
URIEndpointObject address = mock(URIEndpointObject.class);
List<HttpClientFilter> httpClientFilters = new ArrayList<>();
RestClientInvocation restClientInvocation = new RestClientInvocation(httpClientWithContext, httpClientFilters);
Map<String, Object> handlerContext = new HashMap<>();
static long nanoTime = 123;
OperationConfig operationConfig = new OperationConfig();
@BeforeClass
public static void classSetup() {
new MockUp<System>() {
@Mock
long nanoTime() {
return nanoTime;
}
};
}
@SuppressWarnings({"unchecked"})
@Before
public void setup() {
Deencapsulation.setField(restClientInvocation, "clientRequest", request);
Deencapsulation.setField(restClientInvocation, "invocation", invocation);
Deencapsulation.setField(restClientInvocation, "asyncResp", asyncResp);
when(invocation.getMicroserviceName()).thenReturn(TARGET_MICROSERVICE_NAME);
when(invocation.getOperationMeta()).thenReturn(operationMeta);
when(swaggerRestOperation.getPathBuilder()).thenReturn(urlPathBuilder);
when(swaggerRestOperation.getHttpMethod()).thenReturn("GET");
when(operationMeta.getExtData(RestConst.SWAGGER_REST_OPERATION)).thenReturn(swaggerRestOperation);
when(operationMeta.getConfig()).thenReturn(operationConfig);
when(invocation.getEndpoint()).thenReturn(endpoint);
when(invocation.getTraceIdLogger()).thenReturn(new TraceIdLogger(invocation));
when(endpoint.getAddress()).thenReturn(address);
when(invocation.getHandlerContext()).then(answer -> handlerContext);
when(invocation.getInvocationStageTrace()).thenReturn(invocationStageTrace);
when(httpClient.request(ArgumentMatchers.any()))
.thenReturn(Future.succeededFuture(request));
when(request.headers()).thenReturn(headers);
when(request.response()).thenReturn(Future.succeededFuture(httpClientResponse));
doAnswer(a -> {
headers.add(a.getArgument(0, String.class), a.getArgument(1, String.class));
return request;
}).when(request).putHeader(ArgumentMatchers.any(), ArgumentMatchers.anyString());
doAnswer(a -> {
((Handler<Void>) a.getArguments()[0]).handle(null);
return null;
}).when(context).runOnContext(ArgumentMatchers.any());
}
@Test
public void invoke(@Mocked Response resp) throws Exception {
doAnswer(a -> {
asyncResp.complete(resp);
return null;
}).when(request).end();
when(request.send()).thenReturn(Future.succeededFuture(mock(HttpClientResponse.class)));
restClientInvocation.invoke(invocation, asyncResp);
Assertions.assertSame(resp, response);
MatcherAssert.assertThat(headers.names(),
Matchers.containsInAnyOrder(org.apache.servicecomb.core.Const.TARGET_MICROSERVICE,
org.apache.servicecomb.core.Const.CSE_CONTEXT));
Assertions.assertEquals(TARGET_MICROSERVICE_NAME, headers.get(org.apache.servicecomb.core.Const.TARGET_MICROSERVICE));
Assertions.assertEquals("{}", headers.get(org.apache.servicecomb.core.Const.CSE_CONTEXT));
Assertions.assertEquals(nanoTime, invocation.getInvocationStageTrace().getStartClientFiltersRequest());
}
@Test
public void invoke_3rdPartyService(@Mocked Response resp) throws Exception {
doAnswer(a -> {
asyncResp.complete(resp);
return null;
}).when(request).end();
when(invocation.isThirdPartyInvocation()).thenReturn(true);
when(request.send()).thenReturn(Future.succeededFuture(mock(HttpClientResponse.class)));
restClientInvocation.invoke(invocation, asyncResp);
Assertions.assertSame(resp, response);
MatcherAssert.assertThat(headers.names(), Matchers.empty());
Assertions.assertEquals(nanoTime, invocation.getInvocationStageTrace().getStartClientFiltersRequest());
}
@Test
public void invoke_3rdPartyServiceExposeServiceCombHeaders(@Mocked Response resp) throws Exception {
doAnswer(a -> {
asyncResp.complete(resp);
return null;
}).when(request).end();
when(request.send()).thenReturn(Future.succeededFuture(mock(HttpClientResponse.class)));
when(invocation.isThirdPartyInvocation()).thenReturn(true);
operationConfig.setClientRequestHeaderFilterEnabled(false);
restClientInvocation.invoke(invocation, asyncResp);
Assertions.assertSame(resp, response);
MatcherAssert.assertThat(headers.names(),
Matchers.containsInAnyOrder(org.apache.servicecomb.core.Const.TARGET_MICROSERVICE,
org.apache.servicecomb.core.Const.CSE_CONTEXT));
Assertions.assertEquals(TARGET_MICROSERVICE_NAME, headers.get(org.apache.servicecomb.core.Const.TARGET_MICROSERVICE));
Assertions.assertEquals("{}", headers.get(org.apache.servicecomb.core.Const.CSE_CONTEXT));
Assertions.assertEquals(nanoTime, invocation.getInvocationStageTrace().getStartClientFiltersRequest());
operationConfig.setClientRequestHeaderFilterEnabled(true);
}
@Test
public void invoke_endThrow() throws Exception {
Mockito.doThrow(Error.class).when(request).end();
when(request.send()).thenReturn(Future.succeededFuture(mock(HttpClientResponse.class)));
restClientInvocation.invoke(invocation, asyncResp);
MatcherAssert.assertThat(((InvocationException) response.getResult()).getCause(), Matchers.instanceOf(Error.class));
Assertions.assertEquals(nanoTime, invocation.getInvocationStageTrace().getStartClientFiltersRequest());
Assertions.assertEquals(nanoTime, invocation.getInvocationStageTrace().getFinishClientFiltersResponse());
}
@Test
public void invoke_requestThrow() throws Exception {
Throwable t = new RuntimeExceptionWithoutStackTrace();
doAnswer(a -> {
throw t;
}).when(request).end();
when(request.send()).thenReturn(Future.succeededFuture(mock(HttpClientResponse.class)));
restClientInvocation.invoke(invocation, asyncResp);
restClientInvocation.invoke(invocation, asyncResp);
MatcherAssert.assertThat(((InvocationException) response.getResult()).getCause(), Matchers.sameInstance(t));
Assertions.assertEquals(nanoTime, invocation.getInvocationStageTrace().getStartClientFiltersRequest());
Assertions.assertEquals(nanoTime, invocation.getInvocationStageTrace().getFinishClientFiltersResponse());
}
@Test
public void testSetCseContext() {
Map<String, String> contextMap = Collections.singletonMap("k", "v");
when(invocation.getContext()).thenReturn(contextMap);
restClientInvocation.setCseContext();
Assertions.assertEquals("x-cse-context={\"k\":\"v\"}\n", headers.toString());
}
@Test
public void testSetCseContext_failed() throws JsonProcessingException {
LogCollector logCollector = new LogCollector();
logCollector.setLogLevel(RestClientInvocation.class.getName(), Level.DEBUG);
new Expectations(JsonUtils.class) {
{
JsonUtils.writeUnicodeValueAsString(any);
result = new RuntimeExceptionWithoutStackTrace();
}
};
restClientInvocation.setCseContext();
Assertions.assertEquals(
"Failed to encode and set cseContext, message=cause:RuntimeExceptionWithoutStackTrace,message:null.",
logCollector.getEvents().get(0).getMessage());
logCollector.teardown();
}
@Test
public void testSetCseContext_enable_unicode() {
Map<String, String> contextMap = new HashMap<>();
contextMap.put("key", "测试");
contextMap.put("encodedKey", StringEscapeUtils.escapeJson("测试"));
when(invocation.getContext()).thenReturn(contextMap);
restClientInvocation.setCseContext();
String context = headers.get(org.apache.servicecomb.core.Const.CSE_CONTEXT);
HttpServletRequestEx requestEx = new MockUp<HttpServletRequestEx>(){
@Mock
public String getHeader(String name){
if (StringUtils.equals(name, org.apache.servicecomb.core.Const.CSE_CONTEXT)){
return context;
} else {
return null;
}
}
}.getMockInstance();
VertxRestInvocation vertxRestInvocation = new VertxRestInvocation();
Deencapsulation.setField(vertxRestInvocation, "requestEx", requestEx);
Deencapsulation.setField(vertxRestInvocation, "invocation", invocation);
Deencapsulation.invoke(vertxRestInvocation, "setContext");
Assertions.assertEquals("测试", invocation.getContext().get("key"));
Assertions.assertEquals(StringEscapeUtils.escapeJson("测试"), invocation.getContext().get("encodedKey"));
}
@Test
public void testSetCseContext_disable_unicode() throws JsonProcessingException {
Map<String, String> contextMap = new HashMap<>();
contextMap.put("key", "测试");
contextMap.put("encodedKey", StringEscapeUtils.escapeJson("测试"));
when(invocation.getContext()).thenReturn(contextMap);
new MockUp<JsonUtils>() {
@Mock
public String writeUnicodeValueAsString(Object value) throws JsonProcessingException {
return JsonUtils.writeValueAsString(value);
}
};
restClientInvocation.setCseContext();
String context = headers.get(org.apache.servicecomb.core.Const.CSE_CONTEXT);
HttpServletRequestEx requestEx = new MockUp<HttpServletRequestEx>(){
@Mock
public String getHeader(String name){
if (StringUtils.equals(name, org.apache.servicecomb.core.Const.CSE_CONTEXT)){
return context;
} else {
return null;
}
}
}.getMockInstance();
VertxRestInvocation vertxRestInvocation = new VertxRestInvocation();
Deencapsulation.setField(vertxRestInvocation, "requestEx", requestEx);
Deencapsulation.setField(vertxRestInvocation, "invocation", invocation);
Deencapsulation.invoke(vertxRestInvocation, "setContext");
Assertions.assertEquals("测试", invocation.getContext().get("key"));
Assertions.assertEquals(StringEscapeUtils.escapeJson("测试"), invocation.getContext().get("encodedKey"));
}
@SuppressWarnings("unchecked")
@Test
public void handleResponse() {
HttpClientResponse httpClientResponse = mock(HttpClientResponse.class);
doAnswer(a -> {
bodyHandler = (Handler<Buffer>) a.getArguments()[0];
return httpClientResponse;
}).when(httpClientResponse).bodyHandler(ArgumentMatchers.any());
Buffer buf = Buffer.buffer();
new MockUp<RestClientInvocation>(restClientInvocation) {
@Mock
void processResponseBody(Buffer responseBuf) {
asyncResp.success(buf);
}
};
restClientInvocation.handleResponse(httpClientResponse);
bodyHandler.handle(buf);
Assertions.assertSame(buf, response.getResult());
}
@Test
public void processResponseBody() {
Response resp = Response.ok(null);
HttpClientResponse httpClientResponse = mock(HttpClientResponse.class);
Deencapsulation.setField(restClientInvocation, "clientResponse", httpClientResponse);
{
HttpClientFilter filter = mock(HttpClientFilter.class);
when(filter.afterReceiveResponse(ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(null);
when(filter.enabled()).thenReturn(true);
httpClientFilters.add(filter);
}
{
HttpClientFilter filter = mock(HttpClientFilter.class);
when(filter.afterReceiveResponse(ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(resp);
when(filter.enabled()).thenReturn(true);
httpClientFilters.add(filter);
}
when(invocation.getResponseExecutor()).thenReturn(new ReactiveExecutor());
restClientInvocation.processResponseBody(null);
Assertions.assertSame(resp, response);
Assertions.assertEquals(nanoTime, invocation.getInvocationStageTrace().getStartClientFiltersResponse());
Assertions.assertEquals(nanoTime, invocation.getInvocationStageTrace().getFinishClientFiltersResponse());
Assertions.assertEquals(nanoTime, invocation.getInvocationStageTrace().getFinishReceiveResponse());
}
@SuppressWarnings("unchecked")
@Test
public void processResponseBody_throw() {
HttpClientResponse httpClientResponse = mock(HttpClientResponse.class);
Deencapsulation.setField(restClientInvocation, "clientResponse", httpClientResponse);
{
HttpClientFilter filter = mock(HttpClientFilter.class);
when(filter.afterReceiveResponse(ArgumentMatchers.any(), ArgumentMatchers.any())).thenThrow(Error.class);
when(filter.enabled()).thenReturn(true);
httpClientFilters.add(filter);
}
when(invocation.getResponseExecutor()).thenReturn(new ReactiveExecutor());
restClientInvocation.processResponseBody(null);
MatcherAssert.assertThat(((InvocationException) response.getResult()).getCause(), Matchers.instanceOf(Error.class));
Assertions.assertEquals(nanoTime, invocation.getInvocationStageTrace().getStartClientFiltersResponse());
Assertions.assertEquals(nanoTime, invocation.getInvocationStageTrace().getFinishClientFiltersResponse());
Assertions.assertEquals(nanoTime, invocation.getInvocationStageTrace().getFinishReceiveResponse());
Assertions.assertEquals(nanoTime, invocation.getInvocationStageTrace().getFinishWriteToBuffer());
}
@Test
public void createRequestPath_NoUrlPrefixNoPath() throws Exception {
when(address.getFirst(DefinitionConst.URL_PREFIX)).thenReturn(null);
when(urlPathBuilder.createRequestPath(ArgumentMatchers.any())).thenReturn("/path");
String path = restClientInvocation.createRequestPath(swaggerRestOperation);
Assertions.assertEquals("/path", path);
}
@Test
public void createRequestPath_noUrlPrefixHavePath() throws Exception {
handlerContext.put(RestConst.REST_CLIENT_REQUEST_PATH, "/client/path");
when(address.getFirst(DefinitionConst.URL_PREFIX)).thenReturn(null);
String path = restClientInvocation.createRequestPath(swaggerRestOperation);
Assertions.assertEquals("/client/path", path);
}
@Test
public void createRequestPath_haveUrlPrefixNoPath() throws Exception {
when(address.getFirst(DefinitionConst.URL_PREFIX)).thenReturn("/prefix");
when(urlPathBuilder.createRequestPath(ArgumentMatchers.any())).thenReturn("/path");
String path = restClientInvocation.createRequestPath(swaggerRestOperation);
Assertions.assertEquals("/prefix/path", path);
}
@Test
public void createRequestPath_haveUrlPrefixHavePath() throws Exception {
when(address.getFirst(DefinitionConst.URL_PREFIX)).thenReturn("/prefix");
handlerContext.put(RestConst.REST_CLIENT_REQUEST_PATH, "/client/path");
String path = restClientInvocation.createRequestPath(swaggerRestOperation);
Assertions.assertEquals("/prefix/client/path", path);
}
@Test
public void createRequestPath_haveUrlPrefixHavePathAndStartWith() throws Exception {
when(address.getFirst(DefinitionConst.URL_PREFIX)).thenReturn("/prefix");
handlerContext.put(RestConst.REST_CLIENT_REQUEST_PATH, "/prefix/client/path");
String path = restClientInvocation.createRequestPath(swaggerRestOperation);
Assertions.assertEquals("/prefix/client/path", path);
}
}