com.betfair.cougar.client.AbstractHttpExecutableTest.java Source code

Java tutorial

Introduction

Here is the source code for com.betfair.cougar.client.AbstractHttpExecutableTest.java

Source

/*
 * 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.api.ExecutionContextImpl;
import com.betfair.cougar.api.export.Protocol;
import com.betfair.cougar.api.geolocation.GeoLocationDetails;
import com.betfair.cougar.api.security.IdentityResolver;
import com.betfair.cougar.api.security.IdentityChain;
import com.betfair.cougar.api.security.IdentityToken;
import com.betfair.cougar.api.security.IdentityTokenResolver;
import com.betfair.cougar.client.exception.HTTPErrorToCougarExceptionTransformer;
import com.betfair.cougar.client.query.QueryStringGenerator;
import com.betfair.cougar.client.query.QueryStringGeneratorFactory;
import com.betfair.cougar.core.api.ServiceDefinition;
import com.betfair.cougar.core.api.ServiceVersion;
import com.betfair.cougar.core.api.ev.*;
import com.betfair.cougar.core.api.tracing.Tracer;
import com.betfair.cougar.core.api.transcription.Parameter;
import com.betfair.cougar.core.api.transcription.ParameterType;
import com.betfair.cougar.core.impl.DefaultTimeConstraints;
import com.betfair.cougar.logging.CougarLoggingUtils;
import com.betfair.cougar.util.RequestUUIDImpl;
import com.betfair.cougar.util.UUIDGeneratorImpl;
import org.slf4j.LoggerFactory;
import com.betfair.cougar.marshalling.api.databinding.DataBindingFactory;
import com.betfair.cougar.marshalling.api.databinding.FaultMarshaller;
import com.betfair.cougar.marshalling.api.databinding.Marshaller;
import com.betfair.cougar.marshalling.api.databinding.UnMarshaller;
import com.betfair.cougar.transport.api.protocol.http.HttpServiceBindingDescriptor;
import com.betfair.cougar.transport.api.protocol.http.rescript.RescriptBody;
import com.betfair.cougar.transport.api.protocol.http.rescript.RescriptOperationBindingDescriptor;
import com.betfair.cougar.transport.api.protocol.http.rescript.RescriptParamBindingDescriptor;
import com.betfair.cougar.transport.api.protocol.http.rescript.RescriptResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.annotation.XmlElement;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;

/**
 * Date: 28/01/2013
 * Time: 14:48
 */
public abstract class AbstractHttpExecutableTest<HttpRequest> {
    protected AbstractHttpExecutable<HttpRequest> client;
    protected static final String SERVER_URI = "http://localhost:9001/";
    protected static final String TEST_TEXT = "hello";

    protected TestServiceDefinition tsd;

    protected QueryStringGeneratorFactory qsgf;
    protected QueryStringGenerator queryStringGenerator;

    protected Marshaller mockedMarshaller;
    protected UnMarshaller mockedUnMarshaller;
    protected FaultMarshaller mockedFaultMarshaller;
    protected HTTPErrorToCougarExceptionTransformer mockedHttpErrorTransformer;

    protected CougarRequestFactory<HttpRequest> mockMethodFactory;
    protected ExecutionVenue ev;

    protected HttpRequest mockGetMethod;
    protected HttpRequest mockPostMethod;
    protected PassFailExecutionObserver observer;
    protected Tracer tracer;

    @BeforeClass
    public static void suppressLogs() {
        CougarLoggingUtils.suppressAllRootLoggerOutput();
    }

    private Answer<HttpRequest> httpMethodFactoryCreatePostAnswer = new Answer<HttpRequest>() {

        public HttpRequest answer(InvocationOnMock invocation) throws Throwable {
            Object[] args = invocation.getArguments();

            String uri = (String) args[0];
            String httpMethodString = (String) args[1];
            Message message = (Message) args[2];
            Marshaller marshaller = (Marshaller) args[3];
            String contentType = (String) args[4];

            assertEquals(SERVER_URI + "test/v1/post", uri);
            assertEquals("POST", httpMethodString);
            assertEquals(0, message.getHeaderMap().size());
            assertEquals(0, message.getQueryParmMap().size());
            assertEquals(1, message.getRequestBodyMap().size());
            assertTrue(message.getRequestBodyMap().containsKey("messagePOST"));
            assertNotNull(marshaller);
            assertEquals("application/json", contentType);

            return mockPostMethod;
        }
    };

    private Answer<HttpRequest> httpMethodFactoryCreateMixedAnswer = new Answer<HttpRequest>() {

        public HttpRequest answer(InvocationOnMock invocation) throws Throwable {
            Object[] args = invocation.getArguments();

            String uri = (String) args[0];
            String httpMethodString = (String) args[1];
            Message message = (Message) args[2];
            Marshaller marshaller = (Marshaller) args[3];
            String contentType = (String) args[4];

            assertEquals(SERVER_URI + "test/v1/mixed?messageMixedQUERY=hello", uri);
            assertEquals("POST", httpMethodString);
            assertEquals(0, message.getHeaderMap().size());
            assertEquals(1, message.getQueryParmMap().size());
            assertTrue(message.getQueryParmMap().containsKey("messageMixedQUERY"));
            assertEquals(1, message.getRequestBodyMap().size());
            assertTrue(message.getRequestBodyMap().containsKey("messageMixedPOST"));
            assertNotNull(marshaller);
            assertEquals("application/json", contentType);

            return mockPostMethod;
        }
    };

    @Before
    public void setup() throws Exception {
        // Add dependent mocks to cougar client execution venue
        tsd = new TestServiceDefinition();
        TestServiceBindingDescriptor tsbd = new TestServiceBindingDescriptor();
        RequestUUIDImpl.setGenerator(new UUIDGeneratorImpl());

        tracer = mock(Tracer.class);
        client = makeExecutable(tsbd);

        mockedMarshaller = mock(Marshaller.class);
        mockedUnMarshaller = mock(UnMarshaller.class);
        mockedFaultMarshaller = mock(FaultMarshaller.class);
        mockedHttpErrorTransformer = mock(HTTPErrorToCougarExceptionTransformer.class);

        mockMethodFactory = mock(CougarRequestFactory.class);
        when(mockMethodFactory.create(any(String.class), any(String.class), any(Message.class),
                any(Marshaller.class), any(String.class), any(ClientCallContext.class), any(TimeConstraints.class)))
                        .thenReturn(mockGetMethod);
        qsgf = mock(QueryStringGeneratorFactory.class);
        queryStringGenerator = mock(QueryStringGenerator.class);
        when(qsgf.getQueryStringGenerator()).thenReturn(queryStringGenerator);

        client.setRequestFactory(mockMethodFactory);
        client.setQueryStringGeneratorFactory(qsgf);

        DataBindingFactory mockedBindingFactory = mock(DataBindingFactory.class);
        when(mockedBindingFactory.getMarshaller()).thenReturn(mockedMarshaller);
        when(mockedBindingFactory.getUnMarshaller()).thenReturn(mockedUnMarshaller);
        when(mockedBindingFactory.getFaultMarshaller()).thenReturn(mockedFaultMarshaller);
        client.setDataBindingFactory(mockedBindingFactory);

        client.setRemoteAddress(SERVER_URI);

        preInit();

        client.init();
    }

    protected void preInit() throws Exception {

    }

    protected abstract AbstractHttpExecutable<HttpRequest> makeExecutable(HttpServiceBindingDescriptor tsbd)
            throws Exception;

    protected void generateEV(TestServiceDefinition tsd, String namespace) {
        ev = mock(ExecutionVenue.class);

        OperationDefinition[] opDefs = tsd.getOperationDefinitions();
        final Map<OperationKey, OperationDefinition> opMap = new HashMap<OperationKey, OperationDefinition>();
        for (OperationDefinition od : opDefs) {
            if (namespace == null) {
                opMap.put(od.getOperationKey(), od);
            } else {
                opMap.put(new OperationKey(od.getOperationKey(), namespace), od);
            }
        }
        when(ev.getOperationKeys()).thenReturn(opMap.keySet());
        when(ev.getOperationDefinition(any(OperationKey.class))).thenAnswer(new Answer<OperationDefinition>() {
            @Override
            public OperationDefinition answer(InvocationOnMock invocation) throws Throwable {
                OperationKey opKey = (OperationKey) invocation.getArguments()[0];
                return opMap.get(opKey);
            }
        });

    }

    public void makeSimpleCall(ExecutionContext ec) throws Exception {
        String response = "{\"result\":\"" + TEST_TEXT + "\"}";

        when(mockMethodFactory.create(anyString(), anyString(), any(Message.class), any(Marshaller.class),
                anyString(), any(ClientCallContext.class), any(TimeConstraints.class))).thenReturn(mockGetMethod);

        observer = new PassFailExecutionObserver(true, false);
        OperationKey key = TestServiceDefinition.TEST_GET;
        TestResponse tr = new TestResponse();
        tr.setResult(TEST_TEXT);
        when(mockedUnMarshaller.unmarshall(any(InputStream.class), any(ParameterType.class), anyString(), eq(true)))
                .thenReturn(tr);

        mockAndMakeCall(mockGetMethod, HttpServletResponse.SC_OK, response, 18, client, ec, key,
                new Object[] { TEST_TEXT }, observer, ev, DefaultTimeConstraints.NO_CONSTRAINTS);

        TestResponse resp = (TestResponse) observer.getResult().getResult();
        assertEquals(TEST_TEXT, resp.getResult());
        assertEquals(ExecutionResult.ResultType.Success, observer.getResult().getResultType());
        assertEquals(18, ((ClientExecutionResult) observer.getResult()).getResultSize());
    }

    @Test
    public void testEVGetSuccess() throws Exception {
        generateEV(tsd, null);
        makeSimpleCall(createEC(null, null, false));
    }

    @Test
    public void testIdentityTokenWriting() throws Exception {
        IdentityResolver resolver = mock((IdentityResolver.class));
        IdentityTokenResolver tokenResolver = mock((IdentityTokenResolver.class));
        client.setIdentityResolver(resolver);
        client.setIdentityTokenResolver(tokenResolver);
        when(tokenResolver.isRewriteSupported()).thenReturn(true);
        final List<IdentityToken> tokens = new ArrayList<IdentityToken>();
        when(resolver.tokenise(any(IdentityChain.class))).thenReturn(tokens);

        generateEV(tsd, null);
        makeSimpleCall(createEC(null, null, true));
        verify(resolver).tokenise(any(IdentityChain.class));
        verify(tokenResolver).rewrite(eq(tokens), any(HttpUriRequest.class));
    }

    @Test
    public void testIdentityTokenWritingRewriteOff() throws Exception {
        IdentityResolver resolver = mock((IdentityResolver.class));
        IdentityTokenResolver tokenResolver = mock((IdentityTokenResolver.class));
        client.setIdentityResolver(resolver);
        client.setIdentityTokenResolver(tokenResolver);
        when(tokenResolver.isRewriteSupported()).thenReturn(false);
        final List<IdentityToken> tokens = new ArrayList<IdentityToken>();
        when(resolver.tokenise(any(IdentityChain.class))).thenReturn(tokens);

        generateEV(tsd, null);
        makeSimpleCall(createEC(null, null, true));
        verify(resolver, never()).tokenise(any(IdentityChain.class));
        verify(tokenResolver, never()).rewrite(eq(tokens), any(HttpUriRequest.class));
    }

    @Test
    public void testEVGetSuccessWithNamespaceMatch() throws Exception {
        generateEV(tsd, "MyNamespace");
        String response = "{\"result\":\"" + TEST_TEXT + "\"}";

        when(mockMethodFactory.create(anyString(), anyString(), any(Message.class), any(Marshaller.class),
                anyString(), any(ClientCallContext.class), any(TimeConstraints.class))).thenReturn(mockGetMethod);

        TestResponse testResponse = new TestResponse();
        testResponse.setResult(TEST_TEXT);

        OperationKey key = new OperationKey(TestServiceDefinition.TEST_GET, "MyNamespace");
        observer = new PassFailExecutionObserver(true, false);

        TestResponse tr = new TestResponse();
        tr.setResult(TEST_TEXT);
        when(mockedUnMarshaller.unmarshall(any(InputStream.class), any(ParameterType.class), anyString(), eq(true)))
                .thenReturn(tr);

        mockAndMakeCall(mockGetMethod, HttpServletResponse.SC_OK, response, 18, client, createEC(null, null, false),
                key, new Object[] { TEST_TEXT }, observer, ev, DefaultTimeConstraints.NO_CONSTRAINTS);

        TestResponse resp = (TestResponse) observer.getResult().getResult();
        assertEquals(TEST_TEXT, resp.getResult());
        assertEquals(ExecutionResult.ResultType.Success, observer.getResult().getResultType());
        assertEquals(18, ((ClientExecutionResult) observer.getResult()).getResultSize());
    }

    @Test
    public void testEVFailWrongNamespace() {
        generateEV(tsd, "MyNamespace");
        OperationKey key = new OperationKey(TestServiceDefinition.TEST_GET, "WrongNamespace");
        observer = new PassFailExecutionObserver(true, false);

        try {
            client.execute(createEC(null, null, false), key, new Object[] { TEST_TEXT }, observer, ev,
                    DefaultTimeConstraints.NO_CONSTRAINTS);
        } catch (NullPointerException e) {
            // correct - the provided key was not right
            assert true;
        }
    }

    @Test
    public void testEVFailNoNamespaceProvided() {
        generateEV(tsd, "MyNamespace");
        OperationKey key = TestServiceDefinition.TEST_GET;
        observer = new PassFailExecutionObserver(true, false);

        try {
            client.execute(createEC(null, null, false), key, new Object[] { TEST_TEXT }, observer, ev,
                    DefaultTimeConstraints.NO_CONSTRAINTS);
        } catch (NullPointerException e) {
            // correct - the provided key was not right
        }
    }

    @Test
    public void changeRemoteAddress() throws InterruptedException {
        generateEV(tsd, null);

        final int maxThreads = 5;
        Thread[] threads = new Thread[maxThreads];

        final OperationKey key = new OperationKey(TestServiceDefinition.TEST_GET, null);

        final AtomicInteger failureCount = new AtomicInteger(0);

        for (int threadCount = 0; threadCount < maxThreads; threadCount++) {
            final Integer threadId = new Integer(threadCount);
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    int iterations = 100;
                    ExecutionContext ec = createEC(null, null, false);
                    do {
                        observer = new PassFailExecutionObserver(true, true) {
                            @Override
                            public void onResult(ExecutionResult executionResult) {

                            }
                        };

                        try {
                            client.execute(ec, key, new Object[] { TEST_TEXT }, observer, ev,
                                    DefaultTimeConstraints.NO_CONSTRAINTS);
                            if (iterations % 5 == 0 && threadId == 0) {
                                client.setRemoteAddress("http://localhost:" + iterations + "/");
                            }
                        } catch (Exception ex) {
                            failureCount.incrementAndGet();
                        }
                    } while (--iterations > 0);
                }
            });
            threads[threadCount] = t;
            t.start();

        }

        for (int i = 0; i < maxThreads; i++) {
            threads[i].join();
        }

        if (failureCount.get() > 0) {
            fail("An exception occurred during multithreaded test of remote address modification");
        }

    }

    @Test
    public void testEVFailNoNamespaceRegistered() {
        generateEV(tsd, null);
        OperationKey key = new OperationKey(TestServiceDefinition.TEST_GET, "MyNamespace");
        observer = new PassFailExecutionObserver(true, false);

        try {
            client.execute(createEC(null, null, false), key, new Object[] { TEST_TEXT }, observer, ev,
                    DefaultTimeConstraints.NO_CONSTRAINTS);
        } catch (NullPointerException e) {
            // correct - the provided key was not right
        }
    }

    @Test
    public void testEVPostSuccessWithMandatoryBodyParameterPresent() throws Exception {
        generateEV(tsd, null);
        when(queryStringGenerator.generate(any(Map.class))).thenReturn("");

        when(mockMethodFactory.create(anyString(), anyString(), any(Message.class), any(Marshaller.class),
                anyString(), any(ClientCallContext.class), any(TimeConstraints.class)))
                        .thenAnswer(httpMethodFactoryCreatePostAnswer);
        String response = "{\"result\":\"" + TEST_TEXT + "\"}";

        TestResponse tr = new TestResponse();
        tr.setResult(TEST_TEXT);
        when(mockedUnMarshaller.unmarshall(any(InputStream.class), any(ParameterType.class), anyString(), eq(true)))
                .thenReturn(tr);

        TestResponse testResponse = new TestResponse();
        testResponse.setResult(TEST_TEXT);

        OperationKey key = TestServiceDefinition.TEST_POST;
        observer = new PassFailExecutionObserver(true, false);

        mockAndMakeCall(mockPostMethod, HttpServletResponse.SC_OK, response, 18, client,
                createEC(null, null, false), key, new Object[] { TEST_TEXT }, observer, ev,
                DefaultTimeConstraints.NO_CONSTRAINTS);

        TestResponse resp = (TestResponse) observer.getResult().getResult();
        assertEquals(TEST_TEXT, resp.getResult());
        assertEquals(ExecutionResult.ResultType.Success, observer.getResult().getResultType());
        assertEquals(18, ((ClientExecutionResult) observer.getResult()).getResultSize());
    }

    @Test
    public void testEVPostSuccessWithMandatoryBodyAndQueryParameterPresent() throws Exception {
        generateEV(tsd, null);
        when(queryStringGenerator.generate(any(Map.class))).thenReturn("?messageMixedQUERY=hello");
        when(mockMethodFactory.create(anyString(), anyString(), any(Message.class), any(Marshaller.class),
                anyString(), any(ClientCallContext.class), any(TimeConstraints.class)))
                        .thenAnswer(httpMethodFactoryCreateMixedAnswer);
        String response = "{\"result\":\"" + TEST_TEXT + TEST_TEXT + "\"}";

        TestResponse testResponse = new TestResponse();
        testResponse.setResult(TEST_TEXT + TEST_TEXT);

        OperationKey key = TestServiceDefinition.TEST_MIXED;
        observer = new PassFailExecutionObserver(true, false);

        when(mockedUnMarshaller.unmarshall(any(InputStream.class), any(ParameterType.class), anyString(), eq(true)))
                .thenReturn(testResponse);

        mockAndMakeCall(mockPostMethod, HttpServletResponse.SC_OK, response, 18, client,
                createEC(null, null, false), key, new Object[] { TEST_TEXT, TEST_TEXT }, observer, ev,
                DefaultTimeConstraints.NO_CONSTRAINTS);

        TestResponse resp = (TestResponse) observer.getResult().getResult();
        assertEquals(TEST_TEXT + TEST_TEXT, resp.getResult());
        assertEquals(ExecutionResult.ResultType.Success, observer.getResult().getResultType());
        assertEquals(getEVPostSuccessWithMandatoryBodyAndQueryParameterPresent_ResultSize(),
                ((ClientExecutionResult) observer.getResult()).getResultSize());
    }

    protected abstract int getEVPostSuccessWithMandatoryBodyAndQueryParameterPresent_ResultSize();

    @Test(expected = IllegalArgumentException.class)
    public void testExtractPortFromBadAddresses() throws IOException {
        HttpClientExecutable executable = new HttpClientExecutable(null, new HttpContextEmitter(
                new DefaultGeoLocationSerializer(), "X-REQUEST-UUID", "X-REQUEST-UUID-PARENTS"), tracer);
        executable.setRemoteAddress("NOT ASSIGNED");
        executable.extractPortFromAddress();
    }

    @Test
    public void testExtractPortFromAddress() throws IOException {
        HttpClientExecutable executable = new HttpClientExecutable(null, new HttpContextEmitter(
                new DefaultGeoLocationSerializer(), "X-REQUEST-UUID", "X-REQUEST-UUID-PARENTS"), tracer);
        executable.setRemoteAddress("http://wibble.com:3939/www");
        int actual = executable.extractPortFromAddress();
        assertEquals(3939, actual);

        executable.setRemoteAddress("http://wibble.com:25000");
        actual = executable.extractPortFromAddress();
        assertEquals(25000, actual);
    }

    protected ExecutionContext createEC(final String remoteIp, final String customerIp, boolean trace) {
        ExecutionContextImpl eci = new ExecutionContextImpl();
        eci.setTraceLoggingEnabled(trace);
        if (remoteIp != null || customerIp != null) {
            GeoLocationDetails gld = new GeoLocationDetails() {
                @Override
                public String getRemoteAddr() {
                    return remoteIp;
                }

                @Override
                public List<String> getResolvedAddresses() {
                    return Collections.singletonList(customerIp);
                }

                @Override
                public String getCountry() {
                    return null;
                }

                @Override
                public boolean isLowConfidenceGeoLocation() {
                    return false;
                }

                @Override
                public String getLocation() {
                    return null;
                }

                @Override
                public String getInferredCountry() {
                    return null;
                }
            };
            eci.setGeoLocationDetails(gld);
        }
        return eci;
    }

    protected static class TestServiceDefinition extends ServiceDefinition {
        private static final String SERVICE_NAME = "test";
        private static final ServiceVersion SERVICE_VERSION = new ServiceVersion("v1.0");

        public static final OperationKey TEST_GET = new OperationKey(SERVICE_VERSION, SERVICE_NAME, "TEST_GET",
                OperationKey.Type.Request);
        public static final OperationKey TEST_POST = new OperationKey(SERVICE_VERSION, SERVICE_NAME, "TEST_POST",
                OperationKey.Type.Request);
        public static final OperationKey TEST_MIXED = new OperationKey(SERVICE_VERSION, SERVICE_NAME, "TEST_MIXED",
                OperationKey.Type.Request);

        private OperationDefinition[] operationDefinitions = new OperationDefinition[] {
                new SimpleOperationDefinition(TEST_GET,
                        new Parameter[] { new Parameter("message", new ParameterType(String.class, null), true) },
                        new ParameterType(TestResponse.class, null)),

                new SimpleOperationDefinition(TEST_POST,
                        new Parameter[] {
                                new Parameter("messagePOST", new ParameterType(String.class, null), true) },
                        new ParameterType(TestResponse.class, null)),

                new SimpleOperationDefinition(TEST_MIXED,
                        new Parameter[] {
                                new Parameter("messageMixedPOST", new ParameterType(String.class, null), true),
                                new Parameter("messageMixedQUERY", new ParameterType(String.class, null), true) },
                        new ParameterType(TestResponse.class, null)) };

        public TestServiceDefinition() {
            super.init();
        }

        @Override
        public String getServiceName() {
            return SERVICE_NAME;
        }

        @Override
        public ServiceVersion getServiceVersion() {
            return SERVICE_VERSION;
        }

        @Override
        public OperationDefinition[] getOperationDefinitions() {
            return operationDefinitions;
        }
    }

    protected static class PassFailExecutionObserver implements ExecutionObserver, ObservableObserver {

        private boolean passOnResult;
        private boolean passOnException;
        private ExecutionResult result;
        private CountDownLatch latch = new CountDownLatch(1);

        protected PassFailExecutionObserver(boolean passOnResult, boolean passOnException) {
            this.passOnResult = passOnResult;
            this.passOnException = passOnException;

        }

        @Override
        public void onResult(ExecutionResult result) {
            this.result = result;
            latch.countDown();

            switch (result.getResultType()) {
            case Success:
                if (!passOnResult) {
                    fail("Did not expect a successful ExecutionResult");
                }
                break;
            case Fault:
                if (!passOnException) {
                    fail("Did not expect a fault ExecutionResult");
                }
                break;
            default:
                fail("Incorrect ExecutionResult type received: " + result.getResultType());
            }
        }

        public ExecutionResult getResult() {
            return result;
        }

        public CountDownLatch getLatch() {
            return latch;
        }
    }

    protected static class TestServiceBindingDescriptor implements HttpServiceBindingDescriptor {

        private final ServiceVersion serviceVersion = new ServiceVersion("v1.0");
        private final String serviceName = "test";

        public TestServiceBindingDescriptor() {
            List<RescriptParamBindingDescriptor> testGetParamBindings = new ArrayList<RescriptParamBindingDescriptor>();
            testGetParamBindings.add(new RescriptParamBindingDescriptor("message",
                    RescriptParamBindingDescriptor.ParamSource.QUERY));
            testGetDescriptor = new RescriptOperationBindingDescriptor(TestServiceDefinition.TEST_GET, "/get",
                    "GET", testGetParamBindings, TestResponse.class);

            List<RescriptParamBindingDescriptor> testPostParamBindings = new ArrayList<RescriptParamBindingDescriptor>();
            testPostParamBindings.add(new RescriptParamBindingDescriptor("messagePOST",
                    RescriptParamBindingDescriptor.ParamSource.BODY));
            testPostDescriptor = new RescriptOperationBindingDescriptor(TestServiceDefinition.TEST_POST, "/post",
                    "POST", testPostParamBindings, TestResponse.class, TestBody.class);

            List<RescriptParamBindingDescriptor> testMixedParamBindings = new ArrayList<RescriptParamBindingDescriptor>();
            testMixedParamBindings.add(new RescriptParamBindingDescriptor("messageMixedPOST",
                    RescriptParamBindingDescriptor.ParamSource.BODY));
            testMixedParamBindings.add(new RescriptParamBindingDescriptor("messageMixedQUERY",
                    RescriptParamBindingDescriptor.ParamSource.QUERY));
            testMixedDescriptor = new RescriptOperationBindingDescriptor(TestServiceDefinition.TEST_MIXED, "/mixed",
                    "POST", testMixedParamBindings, TestResponse.class, TestBody.class);

            operations = new RescriptOperationBindingDescriptor[] { testGetDescriptor, testPostDescriptor,
                    testMixedDescriptor };
        }

        @Override
        public Protocol getServiceProtocol() {
            return Protocol.RESCRIPT;
        }

        @Override
        public String getServiceContextPath() {
            return "/test/";
        }

        @Override
        public RescriptOperationBindingDescriptor[] getOperationBindings() {
            return operations;
        }

        @Override
        public String getServiceName() {
            return serviceName;
        }

        @Override
        public ServiceVersion getServiceVersion() {
            return serviceVersion;
        }

        private final RescriptOperationBindingDescriptor[] operations;

        private final RescriptOperationBindingDescriptor testGetDescriptor;
        private final RescriptOperationBindingDescriptor testPostDescriptor;
        private final RescriptOperationBindingDescriptor testMixedDescriptor;

    }

    private static class TestBody implements RescriptBody {

        private Map<String, Object> parameters = new HashMap<String, Object>();

        @Override
        public Object getValue(String name) {
            return parameters.get(name);
        }

        private String message;

        @XmlElement(name = "message")
        public String getMessage() {
            return message;
        }

        public void setMessage(String message) {
            this.message = message;
            parameters.put("message", message);
        }
    }

    protected static class TestResponse implements RescriptResponse {

        private Object result;

        @Override
        public Object getResult() {
            return result;
        }

        @Override
        public void setResult(Object result) {
            this.result = result;

        }
    }

    protected static interface ObservableObserver extends ExecutionObserver {
        CountDownLatch getLatch();
    }

    protected abstract void mockAndMakeCall(HttpRequest request, int httpCode, String response, int responseSize,
            AbstractHttpExecutable<HttpRequest> client, ExecutionContext ec, OperationKey key, Object[] params,
            ObservableObserver observer, ExecutionVenue ev, TimeConstraints timeConstraints)
            throws InterruptedException;
}