/*
* Copyright 2014, The Sporting Exchange Limited
* Copyright 2015, Simon Matić Langford
*
* Licensed 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 com.betfair.cougar.client;
import com.betfair.cougar.api.ExecutionContext;
import com.betfair.cougar.core.api.ev.ClientExecutionResult;
import com.betfair.cougar.core.api.ev.ExecutionResult;
import com.betfair.cougar.core.api.ev.ExecutionVenue;
import com.betfair.cougar.core.api.ev.OperationKey;
import com.betfair.cougar.core.api.ev.TimeConstraints;
import com.betfair.cougar.core.api.exception.CougarClientException;
import com.betfair.cougar.core.api.client.ExceptionFactory;
import com.betfair.cougar.core.api.exception.ServerFaultCode;
import com.betfair.cougar.core.impl.DefaultTimeConstraints;
import com.betfair.cougar.transport.api.protocol.http.HttpServiceBindingDescriptor;
import org.eclipse.jetty.client.*;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.InputStreamResponseListener;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpVersion;
import org.junit.After;
import org.junit.Test;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* Date: 28/01/2013
* Time: 15:50
*/
public class AsyncHttpExecutableTest extends AbstractHttpExecutableTest<Request> {
// --------------- Initialisation stuff ----------------
private CapturingRequest mockRequest;
private HttpClient mockClient;
private HttpContextEmitter contextEmitter;
@Override
protected AbstractHttpExecutable<Request> makeExecutable(HttpServiceBindingDescriptor tsbd)
throws Exception {
mockRequest = new CapturingRequest();
mockPostMethod = mockRequest;
mockGetMethod = mockRequest;
contextEmitter = new HttpContextEmitter(new DefaultGeoLocationSerializer(),"X-REQUEST-UUID","X-REQUEST-UUID-PARENTS");
AsyncHttpExecutable executable = new AsyncHttpExecutable(tsbd,contextEmitter, tracer, Executors.newCachedThreadPool(), Executors.newCachedThreadPool());
mockClient = mock(HttpClient.class);
when(mockClient.newRequest(anyString())).thenReturn(mockRequest);
executable.setClient(mockClient);
return executable;
}
@After
public void tearDown() throws Exception {
((AsyncHttpExecutable) client).shutdown();
}
// --------------- Base class requirements ---------------
private void fireResponse(CapturingRequest request, int errorCode, String responseText, int resultSize, ObservableObserver observer, boolean successfulResponse) throws InterruptedException {
Response.CompleteListener listener = request.awaitSend(1000, TimeUnit.MILLISECONDS);
assertNotNull(listener);
InputStreamResponseListener responseListener = (InputStreamResponseListener) listener;
Result result = mock(Result.class);
Response response = mock(Response.class);
when(result.getResponse()).thenReturn(response);
when(result.isSucceeded()).thenReturn(successfulResponse);
when(result.isFailed()).thenReturn(!successfulResponse);
HttpFields headers = mock(HttpFields.class);
when(response.getHeaders()).thenReturn(headers);
when(headers.get(HttpHeader.CONTENT_LENGTH)).thenReturn(String.valueOf(resultSize));
when(response.getStatus()).thenReturn(errorCode);
when(response.getVersion()).thenReturn(HttpVersion.HTTP_1_1);
// fire that event
responseListener.onHeaders(response);
responseListener.onContent(response, ByteBuffer.allocate(0));
responseListener.onComplete(result);
assertTrue(observer.getLatch().await(1000, TimeUnit.MILLISECONDS));
}
protected void mockAndMakeCall(Request request, int httpCode, String response, int responseSize,
final AbstractHttpExecutable<Request> client, final ExecutionContext ec, final OperationKey key,
final Object[] params, final ObservableObserver observer, final ExecutionVenue ev, TimeConstraints timeConstraints) throws InterruptedException {
mockAndMakeCall(request, httpCode, response, responseSize, false, client, ec, key, params, observer, ev, timeConstraints);
}
private void mockAndMakeCall(Request request, int httpCode, String response, int responseSize, boolean ioException,
final AbstractHttpExecutable<Request> client, final ExecutionContext ec, final OperationKey key,
final Object[] params, final ObservableObserver observer, final ExecutionVenue ev, final TimeConstraints timeConstraints) throws InterruptedException {
// calls first (but in new thread)
new Thread(new Runnable() {
@Override
public void run() {
client.execute(ec, key, params, observer, ev, timeConstraints);
}
}).start();
// mocks after
fireResponse((CapturingRequest)request, httpCode, response, responseSize, observer, !ioException);
}
@Override
protected int getEVPostSuccessWithMandatoryBodyAndQueryParameterPresent_ResultSize() {
return 18;
}
// --------------- Specific tests ---------------
@Test
public void testEmptyResponseFromServer() throws IOException, InterruptedException {
generateEV(tsd, null);
when(mockedHttpErrorTransformer.convert(any(InputStream.class), any(ExceptionFactory.class),
anyInt())).thenReturn(new CougarClientException(ServerFaultCode.RemoteCougarCommunicationFailure, "bang"));
final PassFailExecutionObserver observer = new PassFailExecutionObserver(true, false);
new Thread(new Runnable() {
@Override
public void run() {
client.execute(createEC(null, null, false), TestServiceDefinition.TEST_MIXED,
new Object[] {TEST_TEXT, TEST_TEXT }, observer, ev, DefaultTimeConstraints.NO_CONSTRAINTS);
}
}).start();
fireResponse(mockRequest, 200, null, 34, observer, true);
ExecutionResult actual = observer.getResult();
assertFalse(actual.isFault());
assertNull(actual.getResult());
assertEquals(34, ((ClientExecutionResult)actual).getResultSize());
}
@Test
public void testFailingCall() throws IOException, InterruptedException {
generateEV(tsd, null);
when(mockedHttpErrorTransformer.convert(any(InputStream.class), any(ExceptionFactory.class),
anyInt())).thenReturn(new CougarClientException(ServerFaultCode.RemoteCougarCommunicationFailure, "bang"));
final PassFailExecutionObserver observer = new PassFailExecutionObserver(false, true);
new Thread(new Runnable() {
@Override
public void run() {
client.execute(createEC(null, null, false), TestServiceDefinition.TEST_MIXED,
new Object[] {TEST_TEXT, TEST_TEXT }, observer, ev, DefaultTimeConstraints.NO_CONSTRAINTS);
}
}).start();
fireResponse(mockRequest, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null, 34, observer, true);
ExecutionResult actual = observer.getResult();
assertEquals(ExecutionResult.ResultType.Fault, actual.getResultType());
assertNotNull(actual.getFault());
assertNull(actual.getResult());
assertNull(actual.getSubscription());
assertEquals(34, ((ClientExecutionResult)actual).getResultSize());
}
@Test
public void shouldStartupAndShutdown() throws Exception {
final AsyncHttpExecutable executable = new AsyncHttpExecutable(new TestServiceBindingDescriptor(),contextEmitter, tracer, Executors.newCachedThreadPool(), Executors.newCachedThreadPool());
executable.setRemoteAddress("http://localhost");
executable.init();
executable.shutdown();
}
@Test
public void shouldStartupAndShutdownWithTimeout() throws Exception {
final AsyncHttpExecutable executable = new AsyncHttpExecutable(new TestServiceBindingDescriptor(),contextEmitter, tracer, Executors.newCachedThreadPool(), Executors.newCachedThreadPool());
executable.setRemoteAddress("http://localhost");
executable.setConnectTimeout(5000);
executable.setIdleTimeout(5000);
executable.init();
assertEquals(5000, executable.getClient().getConnectTimeout());
assertEquals(5000, executable.getClient().getIdleTimeout());
executable.shutdown();
}
@Test
public void shouldThrowExceptionOnIOException() throws Exception {
generateEV(tsd, null);
final PassFailExecutionObserver mockObserver = new PassFailExecutionObserver(false, true);
mockAndMakeCall(mockRequest, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null, 34, true, client, createEC(null, null, false), TestServiceDefinition.TEST_GET, new Object[]{TEST_TEXT}, mockObserver, ev, DefaultTimeConstraints.NO_CONSTRAINTS);
}
@Test
public void shouldReturnTransportMetrics() throws Exception {
MyHttpDestination dest = mock(MyHttpDestination.class);
when(mockClient.getDestination(anyString(), anyString(), anyInt())).thenReturn(dest);
ConnectionPool pool = mock(ConnectionPool.class);
when(dest.getConnectionPool()).thenReturn(pool);
when(pool.getIdleConnections()).thenReturn(queued(2));
when(pool.getActiveConnections()).thenReturn(queued(4));
((AsyncHttpExecutable) client).setMaxConnectionsPerDestination(10);
assertEquals(4, client.getTransportMetrics().getOpenConnections());
assertEquals(2, client.getTransportMetrics().getFreeConnections());
assertEquals(10, client.getTransportMetrics().getMaximumConnections());
assertEquals(20, client.getTransportMetrics().getCurrentLoad());
}
private BlockingQueue<Connection> queued(int inside) {
BlockingQueue<Connection> ret = new LinkedBlockingDeque<>();
for (int i=0; i<inside; i++) {
ret.add(new Connection() {
@Override
public void send(Request request, Response.CompleteListener listener) {}
@Override
public void close() {}
});
}
return ret;
}
private class MyHttpDestination extends PoolingHttpDestination {
private MyHttpDestination(HttpClient client, String scheme, String host, int port) {
super(client, new Origin(scheme, host, port));
}
@Override
protected void send() {
}
@Override
protected void send(Connection connection, HttpExchange exchange) {
//To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void succeeded(Object result) {
//To change body of implemented methods use File | Settings | File Templates.
}
}
}