mirror of https://github.com/apache/jclouds.git
Issue 1022: added BaseRestApiExpectTest
This commit is contained in:
parent
91a8895cea
commit
72ba1639b9
|
@ -0,0 +1,575 @@
|
|||
/**
|
||||
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||
* contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. jclouds 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.jclouds.rest.internal;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.custommonkey.xmlunit.Diff;
|
||||
import org.custommonkey.xmlunit.Difference;
|
||||
import org.custommonkey.xmlunit.DifferenceConstants;
|
||||
import org.custommonkey.xmlunit.DifferenceListener;
|
||||
import org.custommonkey.xmlunit.NodeDetail;
|
||||
import org.custommonkey.xmlunit.XMLUnit;
|
||||
import org.jclouds.Constants;
|
||||
import org.jclouds.ContextBuilder;
|
||||
import org.jclouds.apis.ApiMetadata;
|
||||
import org.jclouds.concurrent.MoreExecutors;
|
||||
import org.jclouds.concurrent.SingleThreaded;
|
||||
import org.jclouds.concurrent.config.ConfiguresExecutorService;
|
||||
import org.jclouds.date.internal.DateServiceDateCodecFactory;
|
||||
import org.jclouds.date.internal.DateServiceDateCodecFactory.DateServiceIso8601Codec;
|
||||
import org.jclouds.date.internal.DateServiceDateCodecFactory.DateServiceRfc1123Codec;
|
||||
import org.jclouds.date.internal.SimpleDateFormatDateService;
|
||||
import org.jclouds.http.HttpCommandExecutorService;
|
||||
import org.jclouds.http.HttpRequest;
|
||||
import org.jclouds.http.HttpResponse;
|
||||
import org.jclouds.http.HttpUtils;
|
||||
import org.jclouds.http.IOExceptionRetryHandler;
|
||||
import org.jclouds.http.config.ConfiguresHttpCommandExecutorService;
|
||||
import org.jclouds.http.handlers.DelegatingErrorHandler;
|
||||
import org.jclouds.http.handlers.DelegatingRetryHandler;
|
||||
import org.jclouds.http.internal.BaseHttpCommandExecutorService;
|
||||
import org.jclouds.http.internal.HttpWire;
|
||||
import org.jclouds.io.ContentMetadataCodec;
|
||||
import org.jclouds.io.ContentMetadataCodec.DefaultContentMetadataCodec;
|
||||
import org.jclouds.io.CopyInputStreamInputSupplierMap;
|
||||
import org.jclouds.io.Payload;
|
||||
import org.jclouds.io.Payloads;
|
||||
import org.jclouds.logging.config.NullLoggingModule;
|
||||
import org.jclouds.providers.ProviderMetadata;
|
||||
import org.jclouds.rest.RestApiMetadata;
|
||||
import org.jclouds.rest.config.CredentialStoreModule;
|
||||
import org.jclouds.util.Strings2;
|
||||
import org.testng.annotations.Test;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableBiMap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.io.InputSupplier;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Binder;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.name.Names;
|
||||
|
||||
/**
|
||||
*
|
||||
* Allows us to test a client via its side effects.
|
||||
*
|
||||
* <p/>
|
||||
* Example usage:
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
* HttpRequest bucketFooExists = HttpRequest.builder().method("HEAD").endpoint(
|
||||
* URI.create("https://foo.s3.amazonaws.com/?max-keys=0")).headers(
|
||||
* ImmutableMultimap.<String, String> builder().put("Host", "foo.s3.amazonaws.com").put("Date", CONSTANT_DATE)
|
||||
* .put("Authorization", "AWS identity:86P4BBb7xT+gBqq7jxM8Tc28ktY=").build()).build();
|
||||
*
|
||||
* S3Client clientWhenBucketExists = requestSendsResponse(bucketFooExists, HttpResponse.builder().statusCode(200).build());
|
||||
* assert clientWhenBucketExists.bucketExists("foo");
|
||||
*
|
||||
* S3Client clientWhenBucketDoesntExist = requestSendsResponse(bucketFooExists, HttpResponse.builder().statusCode(404)
|
||||
* .build());
|
||||
* assert !clientWhenBucketDoesntExist.bucketExists("foo");
|
||||
* </pre>
|
||||
*
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
@Test(groups = "unit")
|
||||
@Beta
|
||||
public abstract class BaseRestApiExpectTest<S> {
|
||||
|
||||
protected String provider = "mock";
|
||||
|
||||
protected ContentMetadataCodec contentMetadataCodec = new DefaultContentMetadataCodec(
|
||||
new DateServiceDateCodecFactory(new DateServiceRfc1123Codec(new SimpleDateFormatDateService()),
|
||||
new DateServiceIso8601Codec(new SimpleDateFormatDateService())));
|
||||
|
||||
/**
|
||||
* Override this to supply alternative bindings for use in the test. This is commonly used to
|
||||
* override suppliers of dates so that the test results are predicatable.
|
||||
*
|
||||
* @return optional guice module which can override bindings
|
||||
*/
|
||||
protected Module createModule() {
|
||||
return new Module() {
|
||||
|
||||
@Override
|
||||
public void configure(Binder binder) {
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method used when creating a response that includes an http payload.
|
||||
*
|
||||
* <p/>
|
||||
* ex.
|
||||
*
|
||||
* <pre>
|
||||
* HttpResponse.builder().statusCode(200).payload(payloadFromResource("/ip_get_details.json")).build()
|
||||
* </pre>
|
||||
*
|
||||
* @param resource
|
||||
* resource file such as {@code /serverlist.json}
|
||||
* @return payload for use in http responses.
|
||||
*/
|
||||
public Payload payloadFromResource(String resource) {
|
||||
try {
|
||||
return payloadFromString(Strings2.toStringAndClose(getClass().getResourceAsStream(resource)));
|
||||
} catch (IOException e) {
|
||||
throw Throwables.propagate(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Payload payloadFromResourceWithContentType(String resource, String contentType) {
|
||||
try {
|
||||
return payloadFromStringWithContentType(Strings2.toStringAndClose(getClass().getResourceAsStream(resource)),
|
||||
contentType);
|
||||
} catch (IOException e) {
|
||||
throw Throwables.propagate(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Payload payloadFromString(String payload) {
|
||||
return Payloads.newStringPayload(payload);
|
||||
}
|
||||
|
||||
public static Payload payloadFromStringWithContentType(String payload, String contentType) {
|
||||
Payload p = Payloads.newStringPayload(payload);
|
||||
p.getContentMetadata().setContentType(contentType);
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock executor service which uses the supplied function to return http responses.
|
||||
*/
|
||||
@SingleThreaded
|
||||
@Singleton
|
||||
public static class ExpectHttpCommandExecutorService extends BaseHttpCommandExecutorService<HttpRequest> {
|
||||
|
||||
private final Function<HttpRequest, HttpResponse> fn;
|
||||
|
||||
@Inject
|
||||
public ExpectHttpCommandExecutorService(Function<HttpRequest, HttpResponse> fn, HttpUtils utils,
|
||||
ContentMetadataCodec contentMetadataCodec,
|
||||
@Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioExecutor,
|
||||
IOExceptionRetryHandler ioRetryHandler, DelegatingRetryHandler retryHandler,
|
||||
DelegatingErrorHandler errorHandler, HttpWire wire) {
|
||||
super(utils, contentMetadataCodec, ioExecutor, retryHandler, ioRetryHandler, errorHandler, wire);
|
||||
this.fn = checkNotNull(fn, "fn");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(HttpRequest nativeResponse) {
|
||||
if (nativeResponse != null && nativeResponse.getPayload() != null)
|
||||
nativeResponse.getPayload().release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpRequest convert(HttpRequest request) throws IOException, InterruptedException {
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpResponse invoke(HttpRequest nativeRequest) throws IOException, InterruptedException {
|
||||
return fn.apply(nativeRequest);
|
||||
}
|
||||
}
|
||||
|
||||
@ConfiguresHttpCommandExecutorService
|
||||
@ConfiguresExecutorService
|
||||
public static class ExpectModule extends AbstractModule {
|
||||
private final Function<HttpRequest, HttpResponse> fn;
|
||||
|
||||
public ExpectModule(Function<HttpRequest, HttpResponse> fn) {
|
||||
this.fn = checkNotNull(fn, "fn");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure() {
|
||||
bind(ExecutorService.class).annotatedWith(Names.named(Constants.PROPERTY_USER_THREADS)).toInstance(
|
||||
MoreExecutors.sameThreadExecutor());
|
||||
bind(ExecutorService.class).annotatedWith(Names.named(Constants.PROPERTY_IO_WORKER_THREADS)).toInstance(
|
||||
MoreExecutors.sameThreadExecutor());
|
||||
bind(new TypeLiteral<Function<HttpRequest, HttpResponse>>() {
|
||||
}).toInstance(fn);
|
||||
bind(HttpCommandExecutorService.class).to(ExpectHttpCommandExecutorService.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a client for a mock server which only responds to a single http request
|
||||
*
|
||||
* @param request
|
||||
* the http request the mock server responds to
|
||||
* @param response
|
||||
* the response the mock server returns for the request
|
||||
* @return a client configured with this behavior
|
||||
*/
|
||||
public S requestSendsResponse(HttpRequest request, HttpResponse response) {
|
||||
return requestSendsResponse(request, response, createModule());
|
||||
}
|
||||
|
||||
public S requestSendsResponse(HttpRequest request, HttpResponse response, Module module) {
|
||||
return requestsSendResponses(ImmutableMap.of(request, response), module);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a client for a mock server which only responds to two types of requests
|
||||
*
|
||||
* @param requestA
|
||||
* an http request the mock server responds to
|
||||
* @param responseA
|
||||
* the response for {@code requestA}
|
||||
* @param requestB
|
||||
* another http request the mock server responds to
|
||||
* @param responseB
|
||||
* the response for {@code requestB}
|
||||
* @return a client configured with this behavior
|
||||
*/
|
||||
public S requestsSendResponses(HttpRequest requestA, HttpResponse responseA, HttpRequest requestB,
|
||||
HttpResponse responseB) {
|
||||
return requestsSendResponses(requestA, responseA, requestB, responseB, createModule());
|
||||
}
|
||||
|
||||
public S requestsSendResponses(HttpRequest requestA, HttpResponse responseA, HttpRequest requestB,
|
||||
HttpResponse responseB, Module module) {
|
||||
return requestsSendResponses(ImmutableMap.of(requestA, responseA, requestB, responseB), module);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a client for a mock server which only responds to three types of requests
|
||||
*
|
||||
* @param requestA
|
||||
* an http request the mock server responds to
|
||||
* @param responseA
|
||||
* the response for {@code requestA}
|
||||
* @param requestB
|
||||
* another http request the mock server responds to
|
||||
* @param responseB
|
||||
* the response for {@code requestB}
|
||||
* @param requestC
|
||||
* another http request the mock server responds to
|
||||
* @param responseC
|
||||
* the response for {@code requestC}
|
||||
* @return a client configured with this behavior
|
||||
*/
|
||||
public S requestsSendResponses(HttpRequest requestA, HttpResponse responseA, HttpRequest requestB,
|
||||
HttpResponse responseB, HttpRequest requestC, HttpResponse responseC) {
|
||||
return requestsSendResponses(requestA, responseA, requestB, responseB, requestC, responseC, createModule());
|
||||
}
|
||||
|
||||
public S requestsSendResponses(HttpRequest requestA, HttpResponse responseA, HttpRequest requestB,
|
||||
HttpResponse responseB, HttpRequest requestC, HttpResponse responseC, Module module) {
|
||||
return requestsSendResponses(ImmutableMap.of(requestA, responseA, requestB, responseB, requestC, responseC),
|
||||
module);
|
||||
}
|
||||
|
||||
public S orderedRequestsSendResponses(HttpRequest requestA, HttpResponse responseA, HttpRequest requestB,
|
||||
HttpResponse responseB) {
|
||||
return orderedRequestsSendResponses(ImmutableList.of(requestA, requestB), ImmutableList.of(responseA, responseB));
|
||||
}
|
||||
|
||||
public S orderedRequestsSendResponses(HttpRequest requestA, HttpResponse responseA, HttpRequest requestB,
|
||||
HttpResponse responseB, HttpRequest requestC, HttpResponse responseC) {
|
||||
return orderedRequestsSendResponses(ImmutableList.of(requestA, requestB, requestC), ImmutableList.of(responseA,
|
||||
responseB, responseC));
|
||||
}
|
||||
|
||||
public S orderedRequestsSendResponses(HttpRequest requestA, HttpResponse responseA, HttpRequest requestB,
|
||||
HttpResponse responseB, HttpRequest requestC, HttpResponse responseC, HttpRequest requestD,
|
||||
HttpResponse responseD) {
|
||||
return orderedRequestsSendResponses(ImmutableList.of(requestA, requestB, requestC, requestD), ImmutableList.of(
|
||||
responseA, responseB, responseC, responseD));
|
||||
}
|
||||
|
||||
public S orderedRequestsSendResponses(final List<HttpRequest> requests, final List<HttpResponse> responses) {
|
||||
final AtomicInteger counter = new AtomicInteger(0);
|
||||
|
||||
return createClient(new Function<HttpRequest, HttpResponse>() {
|
||||
@Override
|
||||
public HttpResponse apply(HttpRequest input) {
|
||||
int index = counter.getAndIncrement();
|
||||
if (index >= requests.size())
|
||||
return HttpResponse.builder().statusCode(500).message(
|
||||
String.format("request %s is out of range (%s)", index, requests.size())).payload(
|
||||
Payloads.newStringPayload(renderRequest(input))).build();
|
||||
if (!httpRequestsAreEqual(input, requests.get(index))) {
|
||||
assertEquals(renderRequest(input), renderRequest(requests.get(index)));
|
||||
}
|
||||
return responses.get(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a client for a mock server which returns responses for requests based on the supplied
|
||||
* Map parameter.
|
||||
*
|
||||
* @param requestToResponse
|
||||
* valid requests and responses for the mock to respond to
|
||||
* @return a client configured with this behavior
|
||||
*/
|
||||
public S requestsSendResponses(Map<HttpRequest, HttpResponse> requestToResponse) {
|
||||
return requestsSendResponses(requestToResponse, createModule());
|
||||
}
|
||||
|
||||
protected enum HttpRequestComparisonType {
|
||||
XML, JSON, DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* How should this HttpRequest be compared with others?
|
||||
*/
|
||||
protected HttpRequestComparisonType compareHttpRequestAsType(HttpRequest input) {
|
||||
return HttpRequestComparisonType.DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two requests as instructed by {@link #compareHttpRequestAsType(HttpRequest)} - default
|
||||
* is to compare using Objects.equal
|
||||
*/
|
||||
public boolean httpRequestsAreEqual(HttpRequest a, HttpRequest b) {
|
||||
try {
|
||||
if (a == null || b == null || !Objects.equal(a.getRequestLine(), b.getRequestLine())
|
||||
|| !Objects.equal(a.getHeaders(), b.getHeaders())) {
|
||||
return false;
|
||||
}
|
||||
if (a.getPayload() == null || b.getPayload() == null) {
|
||||
return Objects.equal(a, b);
|
||||
}
|
||||
|
||||
switch (compareHttpRequestAsType(a)) {
|
||||
case XML: {
|
||||
Diff diff = XMLUnit.compareXML(Strings2.toStringAndClose(a.getPayload().getInput()), Strings2
|
||||
.toStringAndClose(b.getPayload().getInput()));
|
||||
|
||||
// Ignoring whitespace in elements that have other children, xsi:schemaLocation and
|
||||
// differences in namespace prefixes
|
||||
diff.overrideDifferenceListener(new DifferenceListener() {
|
||||
@Override
|
||||
public int differenceFound(Difference diff) {
|
||||
if (diff.getId() == DifferenceConstants.SCHEMA_LOCATION_ID
|
||||
|| diff.getId() == DifferenceConstants.NAMESPACE_PREFIX_ID) {
|
||||
return RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL;
|
||||
}
|
||||
if (diff.getId() == DifferenceConstants.TEXT_VALUE_ID) {
|
||||
for (NodeDetail detail : ImmutableSet.of(diff.getControlNodeDetail(), diff.getTestNodeDetail())) {
|
||||
if (detail.getNode().getParentNode().getChildNodes().getLength() < 2
|
||||
|| !detail.getValue().trim().isEmpty()) {
|
||||
return RETURN_ACCEPT_DIFFERENCE;
|
||||
}
|
||||
}
|
||||
return RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL;
|
||||
}
|
||||
return RETURN_ACCEPT_DIFFERENCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skippedComparison(Node node, Node node1) {
|
||||
}
|
||||
});
|
||||
|
||||
return diff.identical();
|
||||
}
|
||||
case JSON: {
|
||||
JsonParser parser = new JsonParser();
|
||||
JsonElement payloadA = parser.parse(Strings2.toStringAndClose(a.getPayload().getInput()));
|
||||
JsonElement payloadB = parser.parse(Strings2.toStringAndClose(b.getPayload().getInput()));
|
||||
return Objects.equal(payloadA, payloadB);
|
||||
}
|
||||
default: {
|
||||
return Objects.equal(a, b);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw Throwables.propagate(e);
|
||||
}
|
||||
}
|
||||
|
||||
public S requestsSendResponses(final Map<HttpRequest, HttpResponse> requestToResponse, Module module) {
|
||||
return requestsSendResponses(requestToResponse, module, setupProperties());
|
||||
}
|
||||
|
||||
public S requestsSendResponses(final Map<HttpRequest, HttpResponse> requestToResponse, Module module,
|
||||
Properties props) {
|
||||
return createClient(new Function<HttpRequest, HttpResponse>() {
|
||||
ImmutableBiMap<HttpRequest, HttpResponse> bimap = ImmutableBiMap.copyOf(requestToResponse);
|
||||
|
||||
@Override
|
||||
public HttpResponse apply(HttpRequest input) {
|
||||
HttpResponse response = null;
|
||||
for (HttpRequest request : requestToResponse.keySet()) {
|
||||
if (httpRequestsAreEqual(input, request)) {
|
||||
response = requestToResponse.get(request);
|
||||
}
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
StringBuilder payload = new StringBuilder("\n");
|
||||
payload.append("the following request is not configured:\n");
|
||||
payload.append("----------------------------------------\n");
|
||||
payload.append(renderRequest(input));
|
||||
payload.append("----------------------------------------\n");
|
||||
payload.append("configured requests:\n");
|
||||
for (HttpRequest request : requestToResponse.keySet()) {
|
||||
payload.append("----------------------------------------\n");
|
||||
payload.append(renderRequest(request));
|
||||
}
|
||||
response = HttpResponse.builder().statusCode(500).message("no response configured for request").payload(
|
||||
Payloads.newStringPayload(payload.toString())).build();
|
||||
|
||||
} else if (compareHttpRequestAsType(input) == HttpRequestComparisonType.DEFAULT) {
|
||||
// in case hashCode/equals doesn't do a full content check
|
||||
assertEquals(renderRequest(input), renderRequest(bimap.inverse().get(response)));
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}, module, props);
|
||||
}
|
||||
|
||||
public String renderRequest(HttpRequest request) {
|
||||
StringBuilder builder = new StringBuilder().append(request.getRequestLine()).append('\n');
|
||||
for (Entry<String, String> header : request.getHeaders().entries()) {
|
||||
builder.append(header.getKey()).append(": ").append(header.getValue()).append('\n');
|
||||
}
|
||||
if (request.getPayload() != null) {
|
||||
for (Entry<String, String> header : contentMetadataCodec.toHeaders(
|
||||
request.getPayload().getContentMetadata()).entries()) {
|
||||
builder.append(header.getKey()).append(": ").append(header.getValue()).append('\n');
|
||||
}
|
||||
try {
|
||||
builder.append('\n').append(Strings2.toStringAndClose(request.getPayload().getInput()));
|
||||
} catch (IOException e) {
|
||||
throw Throwables.propagate(e);
|
||||
}
|
||||
|
||||
} else {
|
||||
builder.append('\n');
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public S createClient(Function<HttpRequest, HttpResponse> fn) {
|
||||
return createClient(fn, createModule(), setupProperties());
|
||||
}
|
||||
|
||||
public S createClient(Function<HttpRequest, HttpResponse> fn, Module module) {
|
||||
return createClient(fn, module, setupProperties());
|
||||
}
|
||||
|
||||
public S createClient(Function<HttpRequest, HttpResponse> fn, Properties props) {
|
||||
return createClient(fn, createModule(), props);
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public S createClient(Function<HttpRequest, HttpResponse> fn, Module module, Properties props) {
|
||||
return (S) createInjector(fn, module, props).getInstance(api);
|
||||
}
|
||||
|
||||
protected String identity = "identity";
|
||||
protected String credential = "credential";
|
||||
protected Class<?> api;
|
||||
|
||||
/**
|
||||
* @see org.jclouds.providers.Providers#withId
|
||||
*/
|
||||
protected ProviderMetadata createProviderMetadata() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.jclouds.apis.Apis#withId
|
||||
*/
|
||||
protected ApiMetadata createApiMetadata() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Injector createInjector(Function<HttpRequest, HttpResponse> fn, Module module, Properties props) {
|
||||
ContextBuilder builder = null;
|
||||
if (provider != null)
|
||||
try {
|
||||
builder = ContextBuilder.newBuilder(provider).credentials(identity, credential);
|
||||
} catch (NoSuchElementException e) {
|
||||
Logger
|
||||
.getAnonymousLogger()
|
||||
.warning(
|
||||
"provider ["
|
||||
+ provider
|
||||
+ "] is not setup as META-INF/services/org.jclouds.apis.ApiMetadata or META-INF/services/org.jclouds.providers.ProviderMetadata");
|
||||
}
|
||||
if (builder == null) {
|
||||
ProviderMetadata pm = createProviderMetadata();
|
||||
ApiMetadata am = (pm != null) ? pm.getApiMetadata() : checkNotNull(createApiMetadata(),
|
||||
"either createApiMetadata or createProviderMetadata must be overridden");
|
||||
|
||||
builder = pm != null ? ContextBuilder.newBuilder(pm) : ContextBuilder.newBuilder(RestApiMetadata.class.cast(am));
|
||||
}
|
||||
|
||||
this.api = RestApiMetadata.class.cast(builder.getApiMetadata()).getApi();
|
||||
|
||||
// isolate tests from eachother, as default credentialStore is static
|
||||
return builder.credentials(identity, credential).modules(
|
||||
ImmutableSet.of(new ExpectModule(fn), new NullLoggingModule(), new CredentialStoreModule(new CopyInputStreamInputSupplierMap(
|
||||
new ConcurrentHashMap<String, InputSupplier<InputStream>>())), module)).overrides(setupProperties())
|
||||
.buildInjector();
|
||||
}
|
||||
|
||||
/**
|
||||
* override this to supply context-specific parameters during tests.
|
||||
*/
|
||||
protected Properties setupProperties() {
|
||||
Properties props = new Properties();
|
||||
props.put(Constants.PROPERTY_MAX_RETRIES, 1);
|
||||
return props;
|
||||
}
|
||||
}
|
|
@ -18,558 +18,10 @@
|
|||
*/
|
||||
package org.jclouds.rest.internal;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.custommonkey.xmlunit.Diff;
|
||||
import org.custommonkey.xmlunit.Difference;
|
||||
import org.custommonkey.xmlunit.DifferenceConstants;
|
||||
import org.custommonkey.xmlunit.DifferenceListener;
|
||||
import org.custommonkey.xmlunit.NodeDetail;
|
||||
import org.custommonkey.xmlunit.XMLUnit;
|
||||
import org.jclouds.Constants;
|
||||
import org.jclouds.ContextBuilder;
|
||||
import org.jclouds.apis.ApiMetadata;
|
||||
import org.jclouds.concurrent.MoreExecutors;
|
||||
import org.jclouds.concurrent.SingleThreaded;
|
||||
import org.jclouds.concurrent.config.ConfiguresExecutorService;
|
||||
import org.jclouds.date.internal.DateServiceDateCodecFactory;
|
||||
import org.jclouds.date.internal.DateServiceDateCodecFactory.DateServiceIso8601Codec;
|
||||
import org.jclouds.date.internal.DateServiceDateCodecFactory.DateServiceRfc1123Codec;
|
||||
import org.jclouds.date.internal.SimpleDateFormatDateService;
|
||||
import org.jclouds.http.HttpCommandExecutorService;
|
||||
import org.jclouds.http.HttpRequest;
|
||||
import org.jclouds.http.HttpResponse;
|
||||
import org.jclouds.http.HttpUtils;
|
||||
import org.jclouds.http.IOExceptionRetryHandler;
|
||||
import org.jclouds.http.config.ConfiguresHttpCommandExecutorService;
|
||||
import org.jclouds.http.handlers.DelegatingErrorHandler;
|
||||
import org.jclouds.http.handlers.DelegatingRetryHandler;
|
||||
import org.jclouds.http.internal.BaseHttpCommandExecutorService;
|
||||
import org.jclouds.http.internal.HttpWire;
|
||||
import org.jclouds.io.ContentMetadataCodec;
|
||||
import org.jclouds.io.ContentMetadataCodec.DefaultContentMetadataCodec;
|
||||
import org.jclouds.io.CopyInputStreamInputSupplierMap;
|
||||
import org.jclouds.io.Payload;
|
||||
import org.jclouds.io.Payloads;
|
||||
import org.jclouds.logging.config.NullLoggingModule;
|
||||
import org.jclouds.providers.ProviderMetadata;
|
||||
import org.jclouds.rest.RestApiMetadata;
|
||||
import org.jclouds.rest.config.CredentialStoreModule;
|
||||
import org.jclouds.util.Strings2;
|
||||
import org.testng.annotations.Test;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableBiMap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.io.InputSupplier;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Binder;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.name.Names;
|
||||
|
||||
/**
|
||||
*
|
||||
* Allows us to test a client via its side effects.
|
||||
*
|
||||
* <p/>
|
||||
* Example usage:
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
* HttpRequest bucketFooExists = HttpRequest.builder().method("HEAD").endpoint(
|
||||
* URI.create("https://foo.s3.amazonaws.com/?max-keys=0")).headers(
|
||||
* ImmutableMultimap.<String, String> builder().put("Host", "foo.s3.amazonaws.com").put("Date", CONSTANT_DATE)
|
||||
* .put("Authorization", "AWS identity:86P4BBb7xT+gBqq7jxM8Tc28ktY=").build()).build();
|
||||
*
|
||||
* S3Client clientWhenBucketExists = requestSendsResponse(bucketFooExists, HttpResponse.builder().statusCode(200).build());
|
||||
* assert clientWhenBucketExists.bucketExists("foo");
|
||||
*
|
||||
* S3Client clientWhenBucketDoesntExist = requestSendsResponse(bucketFooExists, HttpResponse.builder().statusCode(404)
|
||||
* .build());
|
||||
* assert !clientWhenBucketDoesntExist.bucketExists("foo");
|
||||
* </pre>
|
||||
*
|
||||
* @author Adrian Cole
|
||||
* Please use {@link BaseRestApiExpectTest}
|
||||
*/
|
||||
@Test(groups = "unit")
|
||||
@Beta
|
||||
public abstract class BaseRestClientExpectTest<S> {
|
||||
|
||||
protected String provider = "mock";
|
||||
|
||||
protected ContentMetadataCodec contentMetadataCodec = new DefaultContentMetadataCodec(
|
||||
new DateServiceDateCodecFactory(new DateServiceRfc1123Codec(new SimpleDateFormatDateService()),
|
||||
new DateServiceIso8601Codec(new SimpleDateFormatDateService())));
|
||||
|
||||
/**
|
||||
* Override this to supply alternative bindings for use in the test. This is commonly used to
|
||||
* override suppliers of dates so that the test results are predicatable.
|
||||
*
|
||||
* @return optional guice module which can override bindings
|
||||
*/
|
||||
protected Module createModule() {
|
||||
return new Module() {
|
||||
|
||||
@Override
|
||||
public void configure(Binder binder) {
|
||||
public abstract class BaseRestClientExpectTest<S> extends BaseRestApiExpectTest<S> {
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method used when creating a response that includes an http payload.
|
||||
*
|
||||
* <p/>
|
||||
* ex.
|
||||
*
|
||||
* <pre>
|
||||
* HttpResponse.builder().statusCode(200).payload(payloadFromResource("/ip_get_details.json")).build()
|
||||
* </pre>
|
||||
*
|
||||
* @param resource
|
||||
* resource file such as {@code /serverlist.json}
|
||||
* @return payload for use in http responses.
|
||||
*/
|
||||
public Payload payloadFromResource(String resource) {
|
||||
try {
|
||||
return payloadFromString(Strings2.toStringAndClose(getClass().getResourceAsStream(resource)));
|
||||
} catch (IOException e) {
|
||||
throw Throwables.propagate(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Payload payloadFromResourceWithContentType(String resource, String contentType) {
|
||||
try {
|
||||
return payloadFromStringWithContentType(Strings2.toStringAndClose(getClass().getResourceAsStream(resource)),
|
||||
contentType);
|
||||
} catch (IOException e) {
|
||||
throw Throwables.propagate(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Payload payloadFromString(String payload) {
|
||||
return Payloads.newStringPayload(payload);
|
||||
}
|
||||
|
||||
public static Payload payloadFromStringWithContentType(String payload, String contentType) {
|
||||
Payload p = Payloads.newStringPayload(payload);
|
||||
p.getContentMetadata().setContentType(contentType);
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock executor service which uses the supplied function to return http responses.
|
||||
*/
|
||||
@SingleThreaded
|
||||
@Singleton
|
||||
public static class ExpectHttpCommandExecutorService extends BaseHttpCommandExecutorService<HttpRequest> {
|
||||
|
||||
private final Function<HttpRequest, HttpResponse> fn;
|
||||
|
||||
@Inject
|
||||
public ExpectHttpCommandExecutorService(Function<HttpRequest, HttpResponse> fn, HttpUtils utils,
|
||||
ContentMetadataCodec contentMetadataCodec,
|
||||
@Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioExecutor,
|
||||
IOExceptionRetryHandler ioRetryHandler, DelegatingRetryHandler retryHandler,
|
||||
DelegatingErrorHandler errorHandler, HttpWire wire) {
|
||||
super(utils, contentMetadataCodec, ioExecutor, retryHandler, ioRetryHandler, errorHandler, wire);
|
||||
this.fn = checkNotNull(fn, "fn");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(HttpRequest nativeResponse) {
|
||||
if (nativeResponse != null && nativeResponse.getPayload() != null)
|
||||
nativeResponse.getPayload().release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpRequest convert(HttpRequest request) throws IOException, InterruptedException {
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpResponse invoke(HttpRequest nativeRequest) throws IOException, InterruptedException {
|
||||
return fn.apply(nativeRequest);
|
||||
}
|
||||
}
|
||||
|
||||
@ConfiguresHttpCommandExecutorService
|
||||
@ConfiguresExecutorService
|
||||
public static class ExpectModule extends AbstractModule {
|
||||
private final Function<HttpRequest, HttpResponse> fn;
|
||||
|
||||
public ExpectModule(Function<HttpRequest, HttpResponse> fn) {
|
||||
this.fn = checkNotNull(fn, "fn");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure() {
|
||||
bind(ExecutorService.class).annotatedWith(Names.named(Constants.PROPERTY_USER_THREADS)).toInstance(
|
||||
MoreExecutors.sameThreadExecutor());
|
||||
bind(ExecutorService.class).annotatedWith(Names.named(Constants.PROPERTY_IO_WORKER_THREADS)).toInstance(
|
||||
MoreExecutors.sameThreadExecutor());
|
||||
bind(new TypeLiteral<Function<HttpRequest, HttpResponse>>() {
|
||||
}).toInstance(fn);
|
||||
bind(HttpCommandExecutorService.class).to(ExpectHttpCommandExecutorService.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a client for a mock server which only responds to a single http request
|
||||
*
|
||||
* @param request
|
||||
* the http request the mock server responds to
|
||||
* @param response
|
||||
* the response the mock server returns for the request
|
||||
* @return a client configured with this behavior
|
||||
*/
|
||||
public S requestSendsResponse(HttpRequest request, HttpResponse response) {
|
||||
return requestSendsResponse(request, response, createModule());
|
||||
}
|
||||
|
||||
public S requestSendsResponse(HttpRequest request, HttpResponse response, Module module) {
|
||||
return requestsSendResponses(ImmutableMap.of(request, response), module);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a client for a mock server which only responds to two types of requests
|
||||
*
|
||||
* @param requestA
|
||||
* an http request the mock server responds to
|
||||
* @param responseA
|
||||
* the response for {@code requestA}
|
||||
* @param requestB
|
||||
* another http request the mock server responds to
|
||||
* @param responseB
|
||||
* the response for {@code requestB}
|
||||
* @return a client configured with this behavior
|
||||
*/
|
||||
public S requestsSendResponses(HttpRequest requestA, HttpResponse responseA, HttpRequest requestB,
|
||||
HttpResponse responseB) {
|
||||
return requestsSendResponses(requestA, responseA, requestB, responseB, createModule());
|
||||
}
|
||||
|
||||
public S requestsSendResponses(HttpRequest requestA, HttpResponse responseA, HttpRequest requestB,
|
||||
HttpResponse responseB, Module module) {
|
||||
return requestsSendResponses(ImmutableMap.of(requestA, responseA, requestB, responseB), module);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a client for a mock server which only responds to three types of requests
|
||||
*
|
||||
* @param requestA
|
||||
* an http request the mock server responds to
|
||||
* @param responseA
|
||||
* the response for {@code requestA}
|
||||
* @param requestB
|
||||
* another http request the mock server responds to
|
||||
* @param responseB
|
||||
* the response for {@code requestB}
|
||||
* @param requestC
|
||||
* another http request the mock server responds to
|
||||
* @param responseC
|
||||
* the response for {@code requestC}
|
||||
* @return a client configured with this behavior
|
||||
*/
|
||||
public S requestsSendResponses(HttpRequest requestA, HttpResponse responseA, HttpRequest requestB,
|
||||
HttpResponse responseB, HttpRequest requestC, HttpResponse responseC) {
|
||||
return requestsSendResponses(requestA, responseA, requestB, responseB, requestC, responseC, createModule());
|
||||
}
|
||||
|
||||
public S requestsSendResponses(HttpRequest requestA, HttpResponse responseA, HttpRequest requestB,
|
||||
HttpResponse responseB, HttpRequest requestC, HttpResponse responseC, Module module) {
|
||||
return requestsSendResponses(ImmutableMap.of(requestA, responseA, requestB, responseB, requestC, responseC),
|
||||
module);
|
||||
}
|
||||
|
||||
public S orderedRequestsSendResponses(HttpRequest requestA, HttpResponse responseA, HttpRequest requestB,
|
||||
HttpResponse responseB) {
|
||||
return orderedRequestsSendResponses(ImmutableList.of(requestA, requestB), ImmutableList.of(responseA, responseB));
|
||||
}
|
||||
|
||||
public S orderedRequestsSendResponses(HttpRequest requestA, HttpResponse responseA, HttpRequest requestB,
|
||||
HttpResponse responseB, HttpRequest requestC, HttpResponse responseC) {
|
||||
return orderedRequestsSendResponses(ImmutableList.of(requestA, requestB, requestC), ImmutableList.of(responseA,
|
||||
responseB, responseC));
|
||||
}
|
||||
|
||||
public S orderedRequestsSendResponses(HttpRequest requestA, HttpResponse responseA, HttpRequest requestB,
|
||||
HttpResponse responseB, HttpRequest requestC, HttpResponse responseC, HttpRequest requestD,
|
||||
HttpResponse responseD) {
|
||||
return orderedRequestsSendResponses(ImmutableList.of(requestA, requestB, requestC, requestD), ImmutableList.of(
|
||||
responseA, responseB, responseC, responseD));
|
||||
}
|
||||
|
||||
public S orderedRequestsSendResponses(final List<HttpRequest> requests, final List<HttpResponse> responses) {
|
||||
final AtomicInteger counter = new AtomicInteger(0);
|
||||
|
||||
return createClient(new Function<HttpRequest, HttpResponse>() {
|
||||
@Override
|
||||
public HttpResponse apply(HttpRequest input) {
|
||||
int index = counter.getAndIncrement();
|
||||
if (index >= requests.size())
|
||||
return HttpResponse.builder().statusCode(500).message(
|
||||
String.format("request %s is out of range (%s)", index, requests.size())).payload(
|
||||
Payloads.newStringPayload(renderRequest(input))).build();
|
||||
if (!httpRequestsAreEqual(input, requests.get(index))) {
|
||||
assertEquals(renderRequest(input), renderRequest(requests.get(index)));
|
||||
}
|
||||
return responses.get(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a client for a mock server which returns responses for requests based on the supplied
|
||||
* Map parameter.
|
||||
*
|
||||
* @param requestToResponse
|
||||
* valid requests and responses for the mock to respond to
|
||||
* @return a client configured with this behavior
|
||||
*/
|
||||
public S requestsSendResponses(Map<HttpRequest, HttpResponse> requestToResponse) {
|
||||
return requestsSendResponses(requestToResponse, createModule());
|
||||
}
|
||||
|
||||
protected enum HttpRequestComparisonType {
|
||||
XML, JSON, DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* How should this HttpRequest be compared with others?
|
||||
*/
|
||||
protected HttpRequestComparisonType compareHttpRequestAsType(HttpRequest input) {
|
||||
return HttpRequestComparisonType.DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two requests as instructed by {@link #compareHttpRequestAsType(HttpRequest)} - default
|
||||
* is to compare using Objects.equal
|
||||
*/
|
||||
public boolean httpRequestsAreEqual(HttpRequest a, HttpRequest b) {
|
||||
try {
|
||||
if (a == null || b == null || !Objects.equal(a.getRequestLine(), b.getRequestLine())
|
||||
|| !Objects.equal(a.getHeaders(), b.getHeaders())) {
|
||||
return false;
|
||||
}
|
||||
if (a.getPayload() == null || b.getPayload() == null) {
|
||||
return Objects.equal(a, b);
|
||||
}
|
||||
|
||||
switch (compareHttpRequestAsType(a)) {
|
||||
case XML: {
|
||||
Diff diff = XMLUnit.compareXML(Strings2.toStringAndClose(a.getPayload().getInput()), Strings2
|
||||
.toStringAndClose(b.getPayload().getInput()));
|
||||
|
||||
// Ignoring whitespace in elements that have other children, xsi:schemaLocation and
|
||||
// differences in namespace prefixes
|
||||
diff.overrideDifferenceListener(new DifferenceListener() {
|
||||
@Override
|
||||
public int differenceFound(Difference diff) {
|
||||
if (diff.getId() == DifferenceConstants.SCHEMA_LOCATION_ID
|
||||
|| diff.getId() == DifferenceConstants.NAMESPACE_PREFIX_ID) {
|
||||
return RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL;
|
||||
}
|
||||
if (diff.getId() == DifferenceConstants.TEXT_VALUE_ID) {
|
||||
for (NodeDetail detail : ImmutableSet.of(diff.getControlNodeDetail(), diff.getTestNodeDetail())) {
|
||||
if (detail.getNode().getParentNode().getChildNodes().getLength() < 2
|
||||
|| !detail.getValue().trim().isEmpty()) {
|
||||
return RETURN_ACCEPT_DIFFERENCE;
|
||||
}
|
||||
}
|
||||
return RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL;
|
||||
}
|
||||
return RETURN_ACCEPT_DIFFERENCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skippedComparison(Node node, Node node1) {
|
||||
}
|
||||
});
|
||||
|
||||
return diff.identical();
|
||||
}
|
||||
case JSON: {
|
||||
JsonParser parser = new JsonParser();
|
||||
JsonElement payloadA = parser.parse(Strings2.toStringAndClose(a.getPayload().getInput()));
|
||||
JsonElement payloadB = parser.parse(Strings2.toStringAndClose(b.getPayload().getInput()));
|
||||
return Objects.equal(payloadA, payloadB);
|
||||
}
|
||||
default: {
|
||||
return Objects.equal(a, b);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw Throwables.propagate(e);
|
||||
}
|
||||
}
|
||||
|
||||
public S requestsSendResponses(final Map<HttpRequest, HttpResponse> requestToResponse, Module module) {
|
||||
return requestsSendResponses(requestToResponse, module, setupProperties());
|
||||
}
|
||||
|
||||
public S requestsSendResponses(final Map<HttpRequest, HttpResponse> requestToResponse, Module module,
|
||||
Properties props) {
|
||||
return createClient(new Function<HttpRequest, HttpResponse>() {
|
||||
ImmutableBiMap<HttpRequest, HttpResponse> bimap = ImmutableBiMap.copyOf(requestToResponse);
|
||||
|
||||
@Override
|
||||
public HttpResponse apply(HttpRequest input) {
|
||||
HttpResponse response = null;
|
||||
for (HttpRequest request : requestToResponse.keySet()) {
|
||||
if (httpRequestsAreEqual(input, request)) {
|
||||
response = requestToResponse.get(request);
|
||||
}
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
StringBuilder payload = new StringBuilder("\n");
|
||||
payload.append("the following request is not configured:\n");
|
||||
payload.append("----------------------------------------\n");
|
||||
payload.append(renderRequest(input));
|
||||
payload.append("----------------------------------------\n");
|
||||
payload.append("configured requests:\n");
|
||||
for (HttpRequest request : requestToResponse.keySet()) {
|
||||
payload.append("----------------------------------------\n");
|
||||
payload.append(renderRequest(request));
|
||||
}
|
||||
response = HttpResponse.builder().statusCode(500).message("no response configured for request").payload(
|
||||
Payloads.newStringPayload(payload.toString())).build();
|
||||
|
||||
} else if (compareHttpRequestAsType(input) == HttpRequestComparisonType.DEFAULT) {
|
||||
// in case hashCode/equals doesn't do a full content check
|
||||
assertEquals(renderRequest(input), renderRequest(bimap.inverse().get(response)));
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}, module, props);
|
||||
}
|
||||
|
||||
public String renderRequest(HttpRequest request) {
|
||||
StringBuilder builder = new StringBuilder().append(request.getRequestLine()).append('\n');
|
||||
for (Entry<String, String> header : request.getHeaders().entries()) {
|
||||
builder.append(header.getKey()).append(": ").append(header.getValue()).append('\n');
|
||||
}
|
||||
if (request.getPayload() != null) {
|
||||
for (Entry<String, String> header : contentMetadataCodec.toHeaders(
|
||||
request.getPayload().getContentMetadata()).entries()) {
|
||||
builder.append(header.getKey()).append(": ").append(header.getValue()).append('\n');
|
||||
}
|
||||
try {
|
||||
builder.append('\n').append(Strings2.toStringAndClose(request.getPayload().getInput()));
|
||||
} catch (IOException e) {
|
||||
throw Throwables.propagate(e);
|
||||
}
|
||||
|
||||
} else {
|
||||
builder.append('\n');
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public S createClient(Function<HttpRequest, HttpResponse> fn) {
|
||||
return createClient(fn, createModule(), setupProperties());
|
||||
}
|
||||
|
||||
public S createClient(Function<HttpRequest, HttpResponse> fn, Module module) {
|
||||
return createClient(fn, module, setupProperties());
|
||||
}
|
||||
|
||||
public S createClient(Function<HttpRequest, HttpResponse> fn, Properties props) {
|
||||
return createClient(fn, createModule(), props);
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public S createClient(Function<HttpRequest, HttpResponse> fn, Module module, Properties props) {
|
||||
return (S) createInjector(fn, module, props).getInstance(api);
|
||||
}
|
||||
|
||||
protected String identity = "identity";
|
||||
protected String credential = "credential";
|
||||
protected Class<?> api;
|
||||
|
||||
/**
|
||||
* @see org.jclouds.providers.Providers#withId
|
||||
*/
|
||||
protected ProviderMetadata createProviderMetadata() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.jclouds.apis.Apis#withId
|
||||
*/
|
||||
protected ApiMetadata createApiMetadata() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Injector createInjector(Function<HttpRequest, HttpResponse> fn, Module module, Properties props) {
|
||||
ContextBuilder builder = null;
|
||||
if (provider != null)
|
||||
try {
|
||||
builder = ContextBuilder.newBuilder(provider).credentials(identity, credential);
|
||||
} catch (NoSuchElementException e) {
|
||||
Logger
|
||||
.getAnonymousLogger()
|
||||
.warning(
|
||||
"provider ["
|
||||
+ provider
|
||||
+ "] is not setup as META-INF/services/org.jclouds.apis.ApiMetadata or META-INF/services/org.jclouds.providers.ProviderMetadata");
|
||||
}
|
||||
if (builder == null) {
|
||||
ProviderMetadata pm = createProviderMetadata();
|
||||
ApiMetadata am = (pm != null) ? pm.getApiMetadata() : checkNotNull(createApiMetadata(),
|
||||
"either createApiMetadata or createProviderMetadata must be overridden");
|
||||
|
||||
builder = pm != null ? ContextBuilder.newBuilder(pm) : ContextBuilder.newBuilder(RestApiMetadata.class.cast(am));
|
||||
}
|
||||
|
||||
this.api = RestApiMetadata.class.cast(builder.getApiMetadata()).getApi();
|
||||
|
||||
// isolate tests from eachother, as default credentialStore is static
|
||||
return builder.credentials(identity, credential).modules(
|
||||
ImmutableSet.of(new ExpectModule(fn), new NullLoggingModule(), new CredentialStoreModule(new CopyInputStreamInputSupplierMap(
|
||||
new ConcurrentHashMap<String, InputSupplier<InputStream>>())), module)).overrides(setupProperties())
|
||||
.buildInjector();
|
||||
}
|
||||
|
||||
/**
|
||||
* override this to supply context-specific parameters during tests.
|
||||
*/
|
||||
protected Properties setupProperties() {
|
||||
Properties props = new Properties();
|
||||
props.put(Constants.PROPERTY_MAX_RETRIES, 1);
|
||||
return props;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue