Index: src/main/java/org/apache/hc/client5/http/testframework/HttpClient5Adapter.java =================================================================== --- src/main/java/org/apache/hc/client5/http/testframework/HttpClient5Adapter.java (revision 0) +++ src/main/java/org/apache/hc/client5/http/testframework/HttpClient5Adapter.java (revision 0) @@ -0,0 +1,154 @@ +/* + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.client5.http.testframework; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.hc.client5.http.impl.sync.CloseableHttpClient; +import org.apache.hc.client5.http.impl.sync.HttpClients; +import org.apache.hc.client5.http.methods.CloseableHttpResponse; +import org.apache.hc.client5.http.methods.RequestBuilder; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.ProtocolVersion; +import org.apache.hc.core5.http.entity.ContentType; +import org.apache.hc.core5.http.entity.EntityUtils; +import org.apache.hc.core5.http.entity.StringEntity; + +// TODO: can this be moved beside HttpClient? If so, throw a better exception than HttpServerTestingFrameworkException. +/** + * Implementation of {@link HttpClientPOJOAdapter} for Apache HttpClient5. + * + * @since 5.0 + */ +public class HttpClient5Adapter extends HttpClientPOJOAdapter { + + /** + * {@inheritDoc} + */ + @Override + public Map execute(final String defaultURI, final Map request) throws Exception { + // check the request for missing items. + if (defaultURI == null) { + throw new HttpServerTestingFrameworkException("defaultURL cannot be null"); + } + if (request == null) { + throw new HttpServerTestingFrameworkException("request cannot be null"); + } + if (! request.containsKey("path")) { + throw new HttpServerTestingFrameworkException("Request path should be set."); + } + if (! request.containsKey("method")) { + throw new HttpServerTestingFrameworkException("Request method should be set."); + } + + // Append the path to the defaultURI. + String tempDefaultURI = defaultURI; + if (! defaultURI.endsWith("/")) { + tempDefaultURI += "/"; + } + final String uri = tempDefaultURI + request.get("path"); + + /* + * Use reflection to call a static method on RequestBuilder that is the + * lowercase of the HTTP method. + */ + final String methodName = request.get("method").toString().toLowerCase(); + + final Method method = RequestBuilder.class.getMethod(methodName); + RequestBuilder builder = ((RequestBuilder) method.invoke(null, new Object[] {})); + + builder = builder.setUri(uri); + + if (request.containsKey("protocolVersion")) { + builder = builder.setVersion((ProtocolVersion) request.get("protocolVersion")); + } + + // call addParameter for each parameter in the query. + @SuppressWarnings("unchecked") + final Map queryMap = (Map) request.get("query"); + if (queryMap != null) { + for (Entry parm : queryMap.entrySet()) { + builder = builder.addParameter(parm.getKey(), parm.getValue()); + } + } + + // call addHeader for each header in headers. + @SuppressWarnings("unchecked") + final Map headersMap = (Map) request.get("headers"); + if (headersMap != null) { + for (Entry header : headersMap.entrySet()) { + builder = builder.addHeader(header.getKey(), header.getValue()); + } + } + + // call setEntity if a body is specified. + final String requestBody = (String) request.get("body"); + if (requestBody != null) { + final String requestContentType = (String) request.get("contentType"); + final StringEntity entity = requestContentType != null ? + new StringEntity(requestBody, ContentType.parse(requestContentType)) : + new StringEntity(requestBody); + builder = builder.setEntity(entity); + } + + // Now execute the request. + final CloseableHttpClient httpclient = HttpClients.createDefault(); + final CloseableHttpResponse response = httpclient.execute(builder.build()); + + // Prepare the response. It will contain status, body, headers, and contentType. + final HttpEntity entity = response.getEntity(); + final String body = entity == null ? null : EntityUtils.toString(entity); + final String contentType = entity == null ? null : entity.getContentType(); + + final Map ret = new HashMap(); + ret.put("status", response.getStatusLine().getStatusCode()); + + // convert the headers to a Map + final Map headerMap = new HashMap(); + for (Header header : response.getAllHeaders()) { + headerMap.put(header.getName(), header.getValue()); + } + ret.put("headers", headerMap); + ret.put("body", body); + ret.put("contentType", contentType); + + return ret ; + } + + /** + * {@inheritDoc} + */ + @Override + public String getHTTPClientName() { + return "HttpClient5"; + } +} Index: src/main/java/org/apache/hc/client5/http/testframework/HttpClient5TestingAdapter.java =================================================================== --- src/main/java/org/apache/hc/client5/http/testframework/HttpClient5TestingAdapter.java (revision 0) +++ src/main/java/org/apache/hc/client5/http/testframework/HttpClient5TestingAdapter.java (revision 0) @@ -0,0 +1,85 @@ +/* + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.client5.http.testframework; + +import java.util.Map; + +/** + * Implementation of {@link HttpServerTestingAdapter} for Apache HttpClient5. + * + * @since 5.0 + */ +public class HttpClient5TestingAdapter extends HttpServerTestingAdapter { + + /* + * The following is not expected to be changed to true, but it is to highlight + * where the execute method can call the requestHandler's assertNothingThrown() + * method if desired. Since this adapter's execute method does not check + * the response, there is no need to call it. + */ + private boolean callAssertNothingThrown; + + public HttpClient5TestingAdapter() { + adapter = new HttpClient5Adapter(); + } + + /** + * {@inheritDoc} + */ + @Override + public Map execute(final String defaultURI, final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) throws HttpServerTestingFrameworkException { + + try { + // Call the adapter's execute method to actually make the HTTP request. + final Map response = adapter.execute(defaultURI, request); + + /* + * Adapters may call assertNothingThrown() if they would like. This would be to + * make sure the following code is not executed in the event there was something + * thrown in the request handler. + * + * Otherwise, the framework will call it when this method returns. So, it is + * optional. + */ + if (callAssertNothingThrown) { + if (requestHandler == null) { + throw new HttpServerTestingFrameworkException("requestHandler cannot be null"); + } + requestHandler.assertNothingThrown(); + } + + return response; + } catch (HttpServerTestingFrameworkException e) { + throw e; + } catch (Exception ex) { + throw new HttpServerTestingFrameworkException(ex); + } + } +} Index: src/main/java/org/apache/hc/client5/http/testframework/HttpClientPOJOAdapter.java =================================================================== --- src/main/java/org/apache/hc/client5/http/testframework/HttpClientPOJOAdapter.java (revision 0) +++ src/main/java/org/apache/hc/client5/http/testframework/HttpClientPOJOAdapter.java (revision 0) @@ -0,0 +1,145 @@ +/* + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.hc.client5.http.testframework; + +import java.util.Map; + +//TODO: can this class be moved beside HttpClient? (in org.apache.hc.client5.http.sync) + +/** + * + *

This adapter expects a request to be made up of POJOs such as Maps and Lists. In Groovy + * the request could be expressed like this:

+ * + *
+ *
+ * def request = [
+ *                   path    : "a/path",
+ *                   method  : "GET",
+ *                   query   : [
+ *                                parm1 : "1",
+ *                                parm2 : "2",
+ *                             ]
+ *                   headers : [
+ *                                header1 : "stuff",
+ *                                header2 : "more_stuff",
+ *                             ]
+ *                   contentType : "application/json",
+ *                   body        : '{"location" : "home" }',
+ *                ]
+ * 
+ * + *

The adapter will translate this request into calls specific to a particular HTTP client.

+ * + *

The response is then returned with POJOs with this structure:

+ * + *
+ *
+ * def response = [
+ *                    status      : 200,
+ *                    headers     : [
+ *                                      header1 : "response_stuff",
+ *                                  ]
+ *                    contentType : "application/json",
+ *                    body        : '{"location" : "work" }',
+ *                ]
+ * 
+ * @since 5.0 + */ +public abstract class HttpClientPOJOAdapter { + /** + * Name of the HTTP Client that this adapter uses. + * + * @return name of the HTTP Client. + */ + public abstract String getHTTPClientName(); + + /** + * Execute an HTTP request. + * + * @param defaultURI the URI used by default. The path in the request is + * usually appended to it. + * @param request the request as specified above. + * + * @return the response to the request as specified above. + * + * @throws Exception in case of a problem + */ + public abstract Map execute(String defaultURI, Map request) throws Exception; + + /** + *

Check if a request is supported.

+ * + *

Usually called directly by a testing framework. If an HTTP client does not support + * a particular request, a non-null reason should be returned. Otherwise, if + * the request is supported, return null.

+ * + *

If this method is overridden, then the execute method should probably call + * assertRequestSupported() at the beginning.

+ * + * @param request the request as specified above. + * + * @return null if the request is supported; Otherwise, return a reason. + */ + public String checkRequestSupport(final Map request) { + return null; + } + + /** + *

Assert that the request is supported

+ * + *

Usually called by the execute method. Throws an exception if the request + * is not supported.

+ * + * @param request the request as specified above. + * @throws Exception if the request is not supported. + */ + public void assertRequestSupported(final Map request) throws Exception { + final String reason = checkRequestSupport(request); + if (reason != null) { + // TODO: if this class is moved beside HttpClient, change this exception. + throw new HttpServerTestingFrameworkException(reason); + } + } + + /** + *

Modify the request.

+ * + *

In a testing context, a testing framework can call this method to allow + * the adapter to change the request. The request is then given to a + * special request handler of the in-process HttpServer which will later check + * an actual HTTP request against what is expected.

+ * + *

In a production context, this is called by the execute method (if at all).

+ * + * @param request the request as specified above. + * @return the same request or a modification of it. + */ + public Map modifyRequest(final Map request) { + return request; + }; +} Index: src/main/java/org/apache/hc/client5/http/testframework/HttpServerTest.java =================================================================== --- src/main/java/org/apache/hc/client5/http/testframework/HttpServerTest.java (revision 0) +++ src/main/java/org/apache/hc/client5/http/testframework/HttpServerTest.java (revision 0) @@ -0,0 +1,159 @@ +/* + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.client5.http.testframework; + +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.hc.client5.http.utils.URLEncodedUtils; +import org.apache.hc.core5.http.NameValuePair; + +/** + *

This class is not expected to be used directly by the user, but its job is to + * supply helpful defaults for tests.

+ * + *

A test is made up of an HTTP request that the HTTP client will send as well + * as a response that is expected.

+ * + *

See {@link HttpClientPOJOAdapter} for details on the request and response.

+ * + *

Generally, if the request does not specify a method, it is assumed to be a GET. + * There are also defaults for headers, query parameters, body, contentType, etc.

+ * + * @since 5.0 + */ +public class HttpServerTest { + private Map request = new HashMap(); + private Map response = new HashMap(); + + /** + * Constructs a test with default values. + */ + public HttpServerTest() { + this(null); + } + + /** + * Constructs a test with values that are passed in as well as defaults + * for values that are not passed in. + * + * @param test Contains a "request" and an expected "response". + * See {@link HttpClientPOJOAdapter} for details. + */ + @SuppressWarnings("unchecked") + public HttpServerTest(final Map test) { + if (test != null) { + if (test.containsKey("request")) { + request = (Map) test.get("request"); + } + if (test.containsKey("response")) { + response = (Map) test.get("response"); + } + } + } + + /** + * Returns a request with defaults for any parameter that is not specified. + * + * @return a "request" map. + * @throws HttpServerTestingFrameworkException a problem such as an invalid URL + */ + public Map initRequest() throws HttpServerTestingFrameworkException { + // initialize to some helpful defaults + final Map ret = new HashMap(); + ret.put("path", HttpServerTestingFramework.DEFAULT_REQUEST_PATH); + ret.put("body", HttpServerTestingFramework.DEFAULT_REQUEST_BODY); + ret.put("contentType", HttpServerTestingFramework.DEFAULT_REQUEST_CONTENT_TYPE); + ret.put("query", new HashMap(HttpServerTestingFramework.DEFAULT_REQUEST_QUERY)); + ret.put("headers", new HashMap(HttpServerTestingFramework.DEFAULT_REQUEST_HEADERS)); + ret.put("protocolVersion", HttpServerTestingFramework.DEFAULT_REQUEST_PROTOCOL_VERSION); + + // GET is the default method. + if (! request.containsKey("method")) { + request.put("method", "GET"); + } + ret.putAll(request); + + moveAnyParametersInPathToQuery(ret); + + return ret; + } + + private void moveAnyParametersInPathToQuery(final Map request) throws HttpServerTestingFrameworkException { + try { + final String path = (String) request.get("path"); + if (path != null) { + final URI uri = path.startsWith("/") ? new URI("http://localhost:8080" + path) : + new URI("http://localhost:8080/"); + final List params = URLEncodedUtils.parse(uri, StandardCharsets.UTF_8); + @SuppressWarnings("unchecked") + final Map queryMap = (Map) request.get("query"); + for (NameValuePair param : params) { + queryMap.put(param.getName(), param.getValue()); + } + if (! params.isEmpty()) { + request.put("path", uri.getPath()); + } + } + } catch (URISyntaxException e) { + throw new HttpServerTestingFrameworkException(e); + } + } + + /** + * Returns an expected response with defaults for any parameter that is not specified. + * + * @return the "response" map. + */ + public Map initResponseExpectations() { + // 200 is the default status. + if (! response.containsKey("status")) { + response.put("status", 200); + } + + final Map responseExpectations = new HashMap(); + // initialize to some helpful defaults + responseExpectations.put("body", HttpServerTestingFramework.DEFAULT_RESPONSE_BODY); + responseExpectations.put("contentType", HttpServerTestingFramework.DEFAULT_RESPONSE_CONTENT_TYPE); + responseExpectations.put("headers", new HashMap(HttpServerTestingFramework.DEFAULT_RESPONSE_HEADERS)); + + // Now override any defaults with what is requested. + responseExpectations.putAll(response); + + return responseExpectations; + } + + @Override + public String toString() { + return "request: " + request + "\nresponse: " + response; + } +} Index: src/main/java/org/apache/hc/client5/http/testframework/HttpServerTestingAdapter.java =================================================================== --- src/main/java/org/apache/hc/client5/http/testframework/HttpServerTestingAdapter.java (revision 0) +++ src/main/java/org/apache/hc/client5/http/testframework/HttpServerTestingAdapter.java (revision 0) @@ -0,0 +1,143 @@ +/* + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.client5.http.testframework; + +import java.util.Map; + +/** +* +*

This adapter assists the testing of an HTTP client. This adapter in turn uses an +* {@link HttpClientPOJOAdapter} to actually use the HTTP client to make the request. +* See {@link HttpClientPOJOAdapter} to see the format of the request and the returned +* response. The format of the returned response is also the format of the parameter +* called responseExpectations.

+* +*

This adapter will generally call the {@link HttpClientPOJOAdapter} methods of the same +* name when these methods are called:

+* +*
+*
+* isRequestSupported
+* modifyRequest
+* execute
+*
+* 
+* +*

See these method's documentation in {@link HttpClientPOJOAdapter} for details.

+* +*

The value that this adapter adds is with the modifyResponseExpectations method. Each +* test will specify the response that is expected. The HttpClient5 adapter is able +* to use these unmodified expectations, but if a different HTTP client (such as Groovy's +* RESTClient which uses HttpClient) for some reason needs to modify the expectations, +* it would be done in the modifyResponseExpectations method.

+* +* @since 5.0 +*/ +public abstract class HttpServerTestingAdapter { + /** + * This adapter will perform the HTTP request and return the response in the + * expected format. + */ + protected HttpClientPOJOAdapter adapter; + + /** + * See the documentation for the same method in {@link HttpClientPOJOAdapter}. This + * method will typically call it. However, this method also has access to the + * test's response expectations if that is needed for some reason. Furthermore, + * this method also has access to the {@link HttpServerTestingRequestHandler} so + * it can optionally call assertNothingThrown() before checking the response + * further. It is optional because the test framework will call it later. + * + * @param defaultURI See execute method of {@link HttpClientPOJOAdapter}. + * @param request See execute method of {@link HttpClientPOJOAdapter}. + * @param requestHandler The request handler that checks the received HTTP request + * with the request that was intended. If there is a + * mismatch of expectations, then the requestHandler will + * throw an exception. If this execute method does not want + * to make further checks of the response in the case + * the responseHandler threw, then the assertNothingThrown() + * method should be called before doing further checks. + * @param responseExpectations The response expectations of the test. + * @return See return of the execute method of {@link HttpClientPOJOAdapter}. + * @throws HttpServerTestingFrameworkException in the case of a problem. + */ + public abstract Map execute(String defaultURI, + Map request, + HttpServerTestingRequestHandler requestHandler, + Map responseExpectations) + throws HttpServerTestingFrameworkException; + + /** + * See the documentation for the same method in {@link HttpClientPOJOAdapter}. + * + * @param request + * @return + */ + public boolean isRequestSupported(final Map request) { + return (adapter == null) ? true : adapter.checkRequestSupport(request) == null; + }; + + /** + * See the documentation for the same method in {@link HttpClientPOJOAdapter}. + * + * @param request + * @return + */ + public Map modifyRequest(final Map request) { + return (adapter == null) ? request : adapter.modifyRequest(request); + }; + + /** + * Generally a test's response expectations should not need to be modified, but + * if a particular HTTP client (such as Groovy's RESTClient which uses HttpClient) + * needs to modify the response expectations, it should do so here. After this + * method returns, the {@link HttpServerTestingRequestHandler} is sent the + * expectations so the request handler will return a response that matches the + * expectations. When the HTTP response is obtained, the received response + * is matched against the expectations. + * + * @param request for the format, see the documentation for {@link HttpClientPOJOAdapter}. + * @param responseExpectations for the format, see the documentation for {@link HttpClientPOJOAdapter}. + * @return the same or modified response expectations. + */ + public Map modifyResponseExpectations(final Map request, + final Map responseExpectations) { + return responseExpectations; + } + + /** + * Getter for the {@link HttpClientPOJOAdapter} that is actually used to make the + * HTTP request. + * + * @return the {@link HttpClientPOJOAdapter}. + */ + public HttpClientPOJOAdapter getHttpClientPOJOAdapter() { + return adapter; + } + +} \ No newline at end of file Index: src/main/java/org/apache/hc/client5/http/testframework/HttpServerTestingFramework.java =================================================================== --- src/main/java/org/apache/hc/client5/http/testframework/HttpServerTestingFramework.java (revision 0) +++ src/main/java/org/apache/hc/client5/http/testframework/HttpServerTestingFramework.java (revision 0) @@ -0,0 +1,484 @@ +/* + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.client5.http.testframework; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.apache.hc.core5.http.HttpVersion; +import org.apache.hc.core5.http.ProtocolVersion; +import org.apache.hc.core5.http.bootstrap.io.HttpServer; +import org.apache.hc.core5.http.bootstrap.io.ServerBootstrap; +import org.apache.hc.core5.http.config.SocketConfig; + +/** + *

This testing framework starts an in-process {@link HttpServer} which will use an + * {@link HttpServerTestingRequestHandler} to check HTTP requests that are sent + * to it. Before the request is sent, the handler is told what request to expect. + * If the received request does not match the request expectations, an exception + * is thrown.

+ * + *

The handler is also told what response to return. This testing framework will + * then check the response it receives with what it desired. If they do not + * match, an exception is thrown.

+ * + *

This has been designed to work with any HTTP client. So, for instance, Groovy's + * HttpBuilder or RESTClient which uses Apache HttpClient can also be tested with this + * testing framework. A different {@link HttpServerTestingAdapter} is used with + * different HTTP clients. If testing Apache HttpClient5, the {@link HttpClient5TestingAdapter} + * is used. Since use of this testing framework with other projects is desired, + * the testframework package has been placed outside the test directory. Care has + * been taken to make sure no testing dependency such as JUnit or EasyMock is used + * in the framework.

+ * + *

The {@link HttpClient5TestingAdapter} that is used is either passed into the + * constructor or set with setAdapter().

+ * + *

By default, this framework will go through a series of tests that will exercise + * all HTTP methods. If the default tests are not desired, then the deleteTests() + * method can be called. Then, custom tests can be added with the addTest() methods. + * Of course additional tests can be added with the addTest() method without first + * calling deleteTests(). In that case, the default tests and the additional tests + * will all run.

+ * + *

Since this framework has been designed to be used with any HTTP client, the test + * is specified with POJO's such as Map, List, and primitives. The test is a Map with + * two keys - request and response. See {@link HttpClientPOJOAdapter} for details + * on the format of the request and response.

+ * + *

Once any additional tests have been added, the runTests() method is called to + * actually do the testing.

+ * + * @since 5.0 + * + */ +public class HttpServerTestingFramework { + /** + * Use the ALL_METHODS list to conveniently cycle through all HTTP methods. + */ + public static final List ALL_METHODS = Arrays.asList("HEAD", "GET", "DELETE", "POST", "PUT", "PATCH"); + + /** + * If an {@link HttpClient5TestingAdapter} is unable to return a response in + * the format this testing framework is needing, then it will need to check the + * item in the response (such as body, status, headers, or contentType) itself and set + * the returned value of the item as ALREADY_CHECKED. + */ + public static final Object ALREADY_CHECKED = new Object(); + + /** + * If a test does not specify a path, this one is used. + */ + public static final String DEFAULT_REQUEST_PATH = "a/path"; + + /** + * If a test does not specify a body, this one is used. + */ + public static final String DEFAULT_REQUEST_BODY = "{\"location\":\"home\"}"; + + /** + * If a test does not specify a request contentType, this one is used. + */ + public static final String DEFAULT_REQUEST_CONTENT_TYPE = "application/json"; + + /** + * If a test does not specify query parameters, these are used. + */ + public static final Map DEFAULT_REQUEST_QUERY; + + /** + * If a test does not specify a request headers, these are used. + */ + public static final Map DEFAULT_REQUEST_HEADERS; + + /** + * If a test does not specify a protocol version, this one is used. + */ + public static final ProtocolVersion DEFAULT_REQUEST_PROTOCOL_VERSION = HttpVersion.HTTP_1_1; + + /** + * If a test does not specify an expected response status, this one is used. + */ + public static final int DEFAULT_RESPONSE_STATUS = 200; + + /** + * If a test does not specify an expected response body, this one is used. + */ + public static final String DEFAULT_RESPONSE_BODY = "{\"location\":\"work\"}"; + + /** + * If a test does not specify an expected response contentType, this one is used. + */ + public static final String DEFAULT_RESPONSE_CONTENT_TYPE = "application/json"; + + /** + * If a test does not specify expected response headers, these are used. + */ + public static final Map DEFAULT_RESPONSE_HEADERS; + + static { + final Map request = new HashMap(); + request.put("p1", "this"); + request.put("p2", "that"); + DEFAULT_REQUEST_QUERY = Collections.unmodifiableMap(request); + + Map headers = new HashMap(); + headers.put("header1", "stuff"); + headers.put("header2", "more stuff"); + DEFAULT_REQUEST_HEADERS = Collections.unmodifiableMap(headers); + + headers = new HashMap(); + headers.put("header3", "header_three"); + headers.put("header4", "header_four"); + DEFAULT_RESPONSE_HEADERS = Collections.unmodifiableMap(headers); + } + + private HttpServerTestingAdapter adapter; + private HttpServerTestingRequestHandler requestHandler = new HttpServerTestingRequestHandler(); + private List tests = new ArrayList(); + + private HttpServer server; + private int port; + + public HttpServerTestingFramework() throws HttpServerTestingFrameworkException { + this(null); + } + + public HttpServerTestingFramework(final HttpServerTestingAdapter adapter) throws HttpServerTestingFrameworkException { + this.adapter = adapter; + + /* + * By default, a set of tests that will exercise each HTTP method are pre-loaded. + */ + for (String method : ALL_METHODS) { + final List statusList = Arrays.asList(200, 201); + for (Integer status : statusList) { + final Map request = new HashMap(); + request.put("method", method); + + final Map response = new HashMap(); + response.put("status", status); + + final Map test = new HashMap(); + test.put("request", request); + test.put("response", response); + + addTest(test); + } + } + } + + /** + * This is not likely to be used except during the testing of this class. + * It is used to inject a mocked request handler. + * + * @param requestHandler + */ + public void setRequestHandler(final HttpServerTestingRequestHandler requestHandler) { + this.requestHandler = requestHandler; + } + + /** + * Run the tests that have been previously added. First, an in-process {@link HttpServer} is + * started. Then, all the tests are completed by passing each test to the adapter + * which will make the HTTP request. + * + * @throws HttpServerTestingFrameworkException if there is a test failure or unexpected problem. + */ + public void runTests() throws HttpServerTestingFrameworkException { + if (adapter == null) { + throw new HttpServerTestingFrameworkException("adapter should not be null"); + } + + startServer(); + + try { + for (HttpServerTest test : tests) { + try { + callAdapter(test); + } catch (Throwable t) { + processThrowable(t, test); + } + } + } finally { + stopServer(); + } + } + + private void processThrowable(final Throwable t, final HttpServerTest test) throws HttpServerTestingFrameworkException { + HttpServerTestingFrameworkException e; + if (t instanceof HttpServerTestingFrameworkException) { + e = (HttpServerTestingFrameworkException) t; + } else { + e = new HttpServerTestingFrameworkException(t); + } + e.setAdapter(adapter); + e.setTest(test); + throw e; + } + + private void startServer() throws HttpServerTestingFrameworkException { + /* + * Start an in-process server and handle all HTTP requests + * with the requestHandler. + */ + final SocketConfig socketConfig = SocketConfig.custom() + .setSoTimeout(15000) + .build(); + + final ServerBootstrap serverBootstrap = ServerBootstrap.bootstrap() + .setSocketConfig(socketConfig) + .registerHandler("/*", requestHandler); + + server = serverBootstrap.create(); + try { + server.start(); + } catch (IOException e) { + throw new HttpServerTestingFrameworkException(e); + } + + port = server.getLocalPort(); + } + + private void stopServer() { + if (server != null) { + server.shutdown(0, TimeUnit.SECONDS); + server = null; + } + } + + private void callAdapter(final HttpServerTest test) throws HttpServerTestingFrameworkException { + Map request = test.initRequest(); + + /* + * If the adapter does not support the particular request, skip the test. + */ + if (! adapter.isRequestSupported(request)) { + return; + } + + /* + * Allow the adapter to modify the request before the request expectations + * are given to the requestHandler. Typically, adapters should not have + * to modify the request. + */ + request = adapter.modifyRequest(request); + + // Tell the request handler what to expect in the request. + requestHandler.setRequestExpectations(request); + + Map responseExpectations = test.initResponseExpectations(); + /* + * Allow the adapter to modify the response expectations before the handler + * is told what to return. Typically, adapters should not have to modify + * the response expectations. + */ + responseExpectations = adapter.modifyResponseExpectations(request, responseExpectations); + + // Tell the request handler what response to return. + requestHandler.setDesiredResponse(responseExpectations); + + /* + * Use the adapter to make the HTTP call. Make sure the responseExpectations are not changed + * since they have already been sent to the request handler and they will later be used + * to check the response. + */ + final String defaultURI = getDefaultURI(); + final Map response = adapter.execute( + defaultURI, + request, + requestHandler, + Collections.unmodifiableMap(responseExpectations)); + /* + * The adapter is welcome to call assertNothingThrown() earlier, but we will + * do it here to make sure it is done. If the handler threw any exception + * while checking the request it received, it will be re-thrown here. + */ + requestHandler.assertNothingThrown(); + + assertResponseMatchesExpectation(request.get("method"), response, responseExpectations); + } + + @SuppressWarnings("unchecked") + private void assertResponseMatchesExpectation(final Object method, final Map actualResponse, + final Map expectedResponse) + throws HttpServerTestingFrameworkException { + if (actualResponse == null) { + throw new HttpServerTestingFrameworkException("response should not be null"); + } + /* + * Now check the items in the response unless the adapter says they + * already checked something. + */ + if (actualResponse.get("status") != HttpServerTestingFramework.ALREADY_CHECKED) { + assertStatusMatchesExpectation(actualResponse.get("status"), expectedResponse.get("status")); + } + if (! method.equals("HEAD")) { + if (actualResponse.get("body") != HttpServerTestingFramework.ALREADY_CHECKED) { + assertBodyMatchesExpectation(actualResponse.get("body"), expectedResponse.get("body")); + } + if (actualResponse.get("contentType") != HttpServerTestingFramework.ALREADY_CHECKED) { + assertContentTypeMatchesExpectation(actualResponse.get("contentType"), expectedResponse.get("contentType")); + } + } + if (actualResponse.get("headers") != HttpServerTestingFramework.ALREADY_CHECKED) { + assertHeadersMatchExpectation((Map) actualResponse.get("headers"), + (Map) expectedResponse.get("headers")); + } + } + + private void assertStatusMatchesExpectation(final Object actualStatus, final Object expectedStatus) + throws HttpServerTestingFrameworkException { + if (actualStatus == null) { + throw new HttpServerTestingFrameworkException("Returned status is null."); + } + if ((expectedStatus != null) && (! actualStatus.equals(expectedStatus))) { + throw new HttpServerTestingFrameworkException("Expected status not found. expected=" + + expectedStatus + "; actual=" + actualStatus); + } + } + + private void assertBodyMatchesExpectation(final Object actualBody, final Object expectedBody) + throws HttpServerTestingFrameworkException { + if (actualBody == null) { + throw new HttpServerTestingFrameworkException("Returned body is null."); + } + if ((expectedBody != null) && (! actualBody.equals(expectedBody))) { + throw new HttpServerTestingFrameworkException("Expected body not found. expected=" + + expectedBody + "; actual=" + actualBody); + } + } + + private void assertContentTypeMatchesExpectation(final Object actualContentType, final Object expectedContentType) + throws HttpServerTestingFrameworkException { + if (expectedContentType != null) { + if (actualContentType == null) { + throw new HttpServerTestingFrameworkException("Returned contentType is null."); + } + if (! actualContentType.equals(expectedContentType)) { + throw new HttpServerTestingFrameworkException("Expected content type not found. expected=" + + expectedContentType + "; actual=" + actualContentType); + } + } + } + + private void assertHeadersMatchExpectation(final Map actualHeaders, + final Map expectedHeaders) + throws HttpServerTestingFrameworkException { + if (expectedHeaders == null) { + return; + } + for (Map.Entry expectedHeader : ((Map) expectedHeaders).entrySet()) { + final String expectedHeaderName = expectedHeader.getKey(); + if (! actualHeaders.containsKey(expectedHeaderName)) { + throw new HttpServerTestingFrameworkException("Expected header not found: name=" + expectedHeaderName); + } + if (! actualHeaders.get(expectedHeaderName).equals(expectedHeaders.get(expectedHeaderName))) { + throw new HttpServerTestingFrameworkException("Header value not expected: name=" + expectedHeaderName + + "; expected=" + expectedHeaders.get(expectedHeaderName) + + "; actual=" + actualHeaders.get(expectedHeaderName)); + } + } + } + + private String getDefaultURI() { + return "http://localhost:" + port + "/"; + } + + /** + * Sets the {@link HttpServerTestingAdapter}. + * + * @param adapter + */ + public void setAdapter(final HttpServerTestingAdapter adapter) { + this.adapter = adapter; + } + + /** + * Deletes all tests. + */ + public void deleteTests() { + tests = new ArrayList(); + } + + /** + * Call to add a test with defaults. + * + * @throws HttpServerTestingFrameworkException + */ + public void addTest() throws HttpServerTestingFrameworkException { + addTest(null); + } + + /** + * Call to add a test. The test is a map with a "request" and a "response" key. + * See {@link HttpClientPOJOAdapter} for details on the format of the request and response. + * + * @param test Map with a "request" and a "response" key. + * @throws HttpServerTestingFrameworkException + */ + @SuppressWarnings("unchecked") + public void addTest(final Map test) throws HttpServerTestingFrameworkException { + final Map testCopy = (Map) deepcopy(test); + + tests.add(new HttpServerTest(testCopy)); + } + + /** + * Used to make a "deep" copy of an object. This testing framework makes deep copies + * of tests that are added as well as requestExpectations Maps and response Maps. + * + * @param orig a serializable object. + * @return a deep copy of the orig object. + * @throws HttpServerTestingFrameworkException + */ + public static Object deepcopy(final Object orig) throws HttpServerTestingFrameworkException { + try { + // this is from http://stackoverflow.com/questions/13155127/deep-copy-map-in-groovy + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + final ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(orig); + oos.flush(); + final ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray()); + final ObjectInputStream ois = new ObjectInputStream(bin); + return ois.readObject(); + } catch (ClassNotFoundException | IOException e) { + throw new HttpServerTestingFrameworkException(e); + } + } +} \ No newline at end of file Index: src/main/java/org/apache/hc/client5/http/testframework/HttpServerTestingFrameworkException.java =================================================================== --- src/main/java/org/apache/hc/client5/http/testframework/HttpServerTestingFrameworkException.java (revision 0) +++ src/main/java/org/apache/hc/client5/http/testframework/HttpServerTestingFrameworkException.java (revision 0) @@ -0,0 +1,92 @@ +/* + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.client5.http.testframework; + +import org.apache.hc.core5.annotation.Immutable; + +/** + *

Signals a problem or an assertion failure while using the {@link HttpServerTestingFramework}.

+ * + *

Optionally, an adapter and a test can be added to the exception. If this is done, + * the adapter name and the test information is appended to the exception message to help + * determine what test is having a problem.

+ * + * @since 5.0 + */ +@Immutable +public class HttpServerTestingFrameworkException extends Exception { + private HttpServerTestingAdapter adapter; + + private HttpServerTest test; + + /** + * + */ + private static final long serialVersionUID = -1010516169283589675L; + + /** + * Creates a WebServerTestingFrameworkException with the specified detail message. + */ + public HttpServerTestingFrameworkException(final String message) { + super(message); + } + + public HttpServerTestingFrameworkException(final Throwable cause) { + super(cause); + } + + @Override + public String getMessage() { + String message = super.getMessage(); + if (adapter != null) { + final HttpClientPOJOAdapter pojoAdapter = adapter.getHttpClientPOJOAdapter(); + final String httpClient = pojoAdapter == null ? null : pojoAdapter.getHTTPClientName(); + if (httpClient != null) { + if (message == null) { + message = "null"; + } + message += "\nHTTP Client=" + httpClient; + } + } + if (test != null) { + if (message == null) { + message = "null"; + } + message += "\ntest:\n" + test; + } + return message; + } + + public void setAdapter(final HttpServerTestingAdapter adapter) { + this.adapter = adapter; + } + + public void setTest(final HttpServerTest test) { + this.test = test; + } +} Index: src/main/java/org/apache/hc/client5/http/testframework/HttpServerTestingRequestHandler.java =================================================================== --- src/main/java/org/apache/hc/client5/http/testframework/HttpServerTestingRequestHandler.java (revision 0) +++ src/main/java/org/apache/hc/client5/http/testframework/HttpServerTestingRequestHandler.java (revision 0) @@ -0,0 +1,259 @@ +/* + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.client5.http.testframework; + +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.hc.client5.http.utils.URLEncodedUtils; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.http.ProtocolVersion; +import org.apache.hc.core5.http.entity.ContentType; +import org.apache.hc.core5.http.entity.EntityUtils; +import org.apache.hc.core5.http.entity.StringEntity; +import org.apache.hc.core5.http.io.HttpRequestHandler; +import org.apache.hc.core5.http.protocol.HttpContext; + +/** + *

This request handler is used with an in-process instance of HttpServer during testing. + * The handler is told what to expect in the request. If the request does not match + * the expectations, the handler will throw an exception which is then caught and + * saved in the "thrown" member. The testing framework will later call assertNothingThrown(). + * If something was thrown earlier by the handler, an exception will be thrown by the method.

+ * + *

The handler is also told what response to return.

+ * + *

See {@link HttpClientPOJOAdapter} for details on the format of the request and response.

+ * + * @since 5.0 + * + */ +public class HttpServerTestingRequestHandler implements HttpRequestHandler { + protected Throwable thrown; + protected Map requestExpectations; + protected Map desiredResponse; + + /** + * Sets the request expectations. + * + * @param requestExpectations the expected values of the request. + * @throws HttpServerTestingFrameworkException + */ + @SuppressWarnings("unchecked") + public void setRequestExpectations(final Map requestExpectations) throws HttpServerTestingFrameworkException { + this.requestExpectations = (Map) HttpServerTestingFramework.deepcopy(requestExpectations); + } + + /** + * Sets the desired response. The handler will return a response that matches this. + * + * @param desiredResponse the desired response. + * @throws HttpServerTestingFrameworkException + */ + @SuppressWarnings("unchecked") + public void setDesiredResponse(final Map desiredResponse) throws HttpServerTestingFrameworkException { + this.desiredResponse = (Map) HttpServerTestingFramework.deepcopy(desiredResponse); + } + + /** + * After the handler returns the response, any exception or failed assertion will be + * in the member called "thrown". A testing framework can later call this method + * which will rethrow the exception that was thrown before. + * + * @throws HttpServerTestingFrameworkException + */ + public void assertNothingThrown() throws HttpServerTestingFrameworkException { + if (thrown != null) { + final HttpServerTestingFrameworkException e = (thrown instanceof HttpServerTestingFrameworkException ? + (HttpServerTestingFrameworkException) thrown : + new HttpServerTestingFrameworkException(thrown)); + thrown = null; + throw e; + } + } + + /** + *

Checks the HTTP request against the requestExpectations that it was previously given. + * If there is a mismatch, an exception will be saved in the "thrown" member.

+ * + *

Also, a response will be returned that matches the desiredResponse.

+ */ + @Override + public void handle(final HttpRequest request, final HttpResponse response, final HttpContext context) + throws HttpException, IOException { + + try { + /* + * Check the method against the method in the requestExpectations. + */ + final String actualMethod = request.getRequestLine().getMethod(); + final String expectedMethod = (String) requestExpectations.get("method"); + if (! actualMethod.equals(expectedMethod)) { + throw new HttpServerTestingFrameworkException("Method not expected. " + + " expected=" + expectedMethod + "; actual=" + actualMethod); + } + + /* + * Set the status to the status that is in the desiredResponse. + */ + final Object desiredStatus = desiredResponse.get("status"); + if (desiredStatus != null) { + response.setStatusCode((int) desiredStatus); + } + + /* + * Check the query parameters against the parametes in requestExpectations. + */ + @SuppressWarnings("unchecked") + final Map expectedQuery = (Map) requestExpectations.get("query"); + if (expectedQuery != null) { + final URI uri = new URI(request.getRequestLine().getUri()); + final List actualParams = URLEncodedUtils.parse(uri, StandardCharsets.UTF_8); + final Map actualParamsMap = new HashMap(); + for (NameValuePair actualParam : actualParams) { + actualParamsMap.put(actualParam.getName(), actualParam.getValue()); + } + for (Map.Entry expectedParam : expectedQuery.entrySet()) { + final String key = expectedParam.getKey(); + if (! actualParamsMap.containsKey(key)) { + throw new HttpServerTestingFrameworkException("Expected parameter not found: " + key); + } + final String actualParamValue = actualParamsMap.get(key); + final String expectedParamValue = expectedParam.getValue(); + if (! actualParamValue.equals(expectedParamValue)) { + throw new HttpServerTestingFrameworkException("Expected parameter value not found. " + + " Parameter=" + key + "; expected=" + expectedParamValue + "; actual=" + actualParamValue); + } + } + } + + /* + * Check the headers against the headers in requestExpectations. + */ + @SuppressWarnings("unchecked") + final Map expectedHeaders = (Map) requestExpectations.get("headers"); + if (expectedHeaders != null) { + final Map actualHeadersMap = new HashMap(); + final Header[] actualHeaders = request.getAllHeaders(); + for (Header header : actualHeaders) { + actualHeadersMap.put(header.getName(), header.getValue()); + } + for (Entry expectedHeader : expectedHeaders.entrySet()) { + final String key = expectedHeader.getKey(); + if (! actualHeadersMap.containsKey(key)) { + throw new HttpServerTestingFrameworkException("Expected header not found: " + key); + } + final String actualHeaderValue = actualHeadersMap.get(key); + final String expectedHeaderValue = expectedHeader.getValue(); + if (! actualHeaderValue.equals(expectedHeaderValue)) { + throw new HttpServerTestingFrameworkException("Expected header value not found. " + + " Name=" + key + "; expected=" + expectedHeaderValue + "; actual=" + actualHeaderValue); + } + } + } + + /* + * Check the body. + */ + final String expectedBody = (String) requestExpectations.get("body"); + if (expectedBody != null) { + final HttpEntity entity = request.getEntity(); + final String data = EntityUtils.toString(entity); + if (! data.equals(expectedBody)) { + throw new HttpServerTestingFrameworkException("Expected body not found. " + + " Body=" + data + "; expected=" + expectedBody); + } + } + + /* + * Check the contentType of the request. + */ + final String requestContentType = (String) requestExpectations.get("contentType"); + if (requestContentType != null) { + final HttpEntity entity = request.getEntity(); + final String contentType = entity.getContentType(); + final String expectedContentType = (String) requestExpectations.get("contentType"); + if (! contentType.equals(expectedContentType)) { + throw new HttpServerTestingFrameworkException("Expected request content type not found. " + + " Content Type=" + contentType + "; expected=" + expectedContentType); + } + } + + /* + * Check the protocolVersion. + */ + if (requestExpectations.containsKey("protocolVersion")) { + final ProtocolVersion protocolVersion = request.getRequestLine().getProtocolVersion(); + final ProtocolVersion expectedProtocolVersion = (ProtocolVersion) requestExpectations.get("protocolVersion"); + if (! protocolVersion.equals(expectedProtocolVersion)) { + throw new HttpServerTestingFrameworkException("Expected request protocol version not found. " + + " Protocol Version=" + protocolVersion + "; expected=" + expectedProtocolVersion); + } + } + + /* + * Return the body in desiredResponse using the contentType in desiredResponse. + */ + final String desiredBody = (String) desiredResponse.get("body"); + if (desiredBody != null) { + final String desiredContentType = (String) desiredResponse.get("contentType"); + final StringEntity entity = desiredContentType != null ? + new StringEntity(desiredBody, ContentType.parse(desiredContentType)) : + new StringEntity(desiredBody); + response.setEntity(entity); + } + + /* + * Return the headers in desiredResponse. + */ + @SuppressWarnings("unchecked") + final Map desiredHeaders = (Map) desiredResponse.get("headers"); + if (desiredHeaders != null) { + for (Entry entry : desiredHeaders.entrySet()) { + response.setHeader(entry.getKey(), entry.getValue()); + } + } + + } catch (Throwable t) { + /* + * Save the throwable to be later retrieved by a call to assertNothingThrown(). + */ + thrown = t; + } + } +} Index: src/test/java/org/apache/hc/client5/http/testframework/TestHttpClient5Adapter.java =================================================================== --- src/test/java/org/apache/hc/client5/http/testframework/TestHttpClient5Adapter.java (revision 0) +++ src/test/java/org/apache/hc/client5/http/testframework/TestHttpClient5Adapter.java (revision 0) @@ -0,0 +1,261 @@ +/* + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.client5.http.testframework; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.mockito.Mockito; +import org.apache.hc.client5.http.localserver.LocalServerTestBase; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.junit.Assert; + +public class TestHttpClient5Adapter extends LocalServerTestBase { + private static final String ECHO_PATH = "echo/something"; + private static final String CUSTOM_PATH = "custom/something"; + + @Test + public void nullDefaultURI() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter(); + + final String defaultURI = null; + final Map request = new HashMap(); + final HttpServerTestingRequestHandler requestHandler = Mockito.mock(HttpServerTestingRequestHandler.class); + final Map responseExpectations = new HashMap(); + + try { + adapter.execute(defaultURI, request, requestHandler, responseExpectations); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @Test + public void nullRequest() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter(); + + final String defaultURI = ""; + final Map request = null; + final HttpServerTestingRequestHandler requestHandler = Mockito.mock(HttpServerTestingRequestHandler.class); + final Map responseExpectations = new HashMap(); + + try { + adapter.execute(defaultURI, request, requestHandler, responseExpectations); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @Test + public void nullRequestHandler() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter(); + + final String defaultURI = ""; + final Map request = new HashMap(); + final HttpServerTestingRequestHandler requestHandler = null; + final Map responseExpectations = new HashMap(); + + try { + adapter.execute(defaultURI, request, requestHandler, responseExpectations); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @Test + public void nullResponseExpectations() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter(); + + final String defaultURI = ""; + final Map request = new HashMap(); + final HttpServerTestingRequestHandler requestHandler = Mockito.mock(HttpServerTestingRequestHandler.class); + final Map responseExpectations = null; + + try { + adapter.execute(defaultURI, request, requestHandler, responseExpectations); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @Test + public void noPath() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter(); + + final String defaultURI = ""; + final Map request = new HashMap(); + final HttpServerTestingRequestHandler requestHandler = Mockito.mock(HttpServerTestingRequestHandler.class); + final Map responseExpectations = new HashMap(); + + try { + adapter.execute(defaultURI, request, requestHandler, responseExpectations); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @Test + public void noMethod() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter(); + + final String defaultURI = ""; + + final Map request = new HashMap(); + request.put("path", ECHO_PATH); + + final HttpServerTestingRequestHandler requestHandler = Mockito.mock(HttpServerTestingRequestHandler.class); + final Map responseExpectations = new HashMap(); + + try { + adapter.execute(defaultURI, request, requestHandler, responseExpectations); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @Test + public void invalidMethod() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter(); + + final String defaultURI = ""; + + final Map request = new HashMap(); + request.put("path", ECHO_PATH); + request.put("method", "JUNK"); + + final HttpServerTestingRequestHandler requestHandler = Mockito.mock(HttpServerTestingRequestHandler.class); + final Map responseExpectations = new HashMap(); + + try { + adapter.execute(defaultURI, request, requestHandler, responseExpectations); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @Test + public void withLiveServerEcho() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter(); + + final HttpHost target = start(); + + final String defaultURI = target.toString(); + final Map request = new HashMap(); + request.put("path", ECHO_PATH); + request.put("method", "POST"); + final String body = "mybody"; + request.put("body", body); + + final HttpServerTestingRequestHandler requestHandler = Mockito.mock(HttpServerTestingRequestHandler.class); + final Map responseExpectations = new HashMap(); + + final Map response = adapter.execute(defaultURI, request, requestHandler, responseExpectations); + + Assert.assertNotNull("response should not be null", response); + Assert.assertEquals("status unexpected", 200, response.get("status")); + + @SuppressWarnings("unchecked") + final Map headers = (Map) response.get("headers"); + Assert.assertNotNull("headers should be in the response", headers); + Assert.assertFalse(headers.isEmpty()); + + final String returnedBody = (String) response.get("body"); + Assert.assertNotNull("body should be in the response", returnedBody); + Assert.assertEquals("Body should be echoed", body, returnedBody); + + server.shutdown(0, TimeUnit.SECONDS); // 0 seconds to speed up the test. + server = null; + } + + @Test + public void withLiveServerCustomRequestHandler() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter(); + + final HttpServerTestingRequestHandler requestHandler = new HttpServerTestingRequestHandler() { + @Override + public void handle(final HttpRequest request, final HttpResponse response, final HttpContext context) + throws HttpException, IOException { + try { + Assert.assertEquals("method not expected", "junk", request.getRequestLine().getMethod()); + } catch (Throwable t) { + thrown = t; + } + } + }; + serverBootstrap.registerHandler("/custom/*", requestHandler); + + final HttpHost target = start(); + final String defaultURI = target.toString(); + final Map responseExpectations = new HashMap(); + + final Map request = new HashMap(); + request.put("path", CUSTOM_PATH); + + for (String method : HttpServerTestingFramework.ALL_METHODS) { + request.put("method", method); + + adapter.execute(defaultURI, request, requestHandler, responseExpectations); + } + server.shutdown(0, TimeUnit.SECONDS); // 0 seconds to speed up the test. + server = null; + } + + @Test + public void modifyRequest() { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter(); + + final Map request = new HashMap(); + final Map returnedRequest = adapter.modifyRequest(request); + + Assert.assertSame("Same request was not returned as expected.", request, returnedRequest); + } + + @Test + public void modifyResponseExpectations() { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter(); + + final Map responseExpectations = new HashMap(); + final Map returnedResponseExpectations = adapter.modifyResponseExpectations(null, responseExpectations); + + Assert.assertSame("Same response expectations were not returned as expected.", responseExpectations, returnedResponseExpectations); + } +} Index: src/test/java/org/apache/hc/client5/http/testframework/TestHttpClientPOJOAdapter.java =================================================================== --- src/test/java/org/apache/hc/client5/http/testframework/TestHttpClientPOJOAdapter.java (revision 0) +++ src/test/java/org/apache/hc/client5/http/testframework/TestHttpClientPOJOAdapter.java (revision 0) @@ -0,0 +1,82 @@ +/* + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.hc.client5.http.testframework; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; + +public class TestHttpClientPOJOAdapter { + @Test + public void modifyRequest() throws Exception { + final HttpClientPOJOAdapter adapter = new HttpClient5Adapter(); + final Map request = new HashMap(); + final Map request2 = adapter.modifyRequest(request); + + Assert.assertSame("request should have been returned", request, request2); + } + + @Test + public void checkRequestSupport() throws Exception { + final HttpClientPOJOAdapter adapter = new HttpClient5Adapter(); + final String reason = adapter.checkRequestSupport(null); + + Assert.assertNull("reason should be null", reason); + + adapter.assertRequestSupported(null); + } + + @Test + public void checkRequestSupportThrows() throws Exception { + final HttpClientPOJOAdapter adapter = new HttpClientPOJOAdapter() { + + @Override + public Map execute(final String defaultURI, final Map request) throws Exception { + return null; + } + + @Override + public String checkRequestSupport(final java.util.Map request) { + return "A reason this request is not supported."; + } + + @Override + public String getHTTPClientName() { + return null; + }; + }; + + try { + adapter.assertRequestSupported(null); + Assert.fail("A WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } +} Index: src/test/java/org/apache/hc/client5/http/testframework/TestHttpServerTest.java =================================================================== --- src/test/java/org/apache/hc/client5/http/testframework/TestHttpServerTest.java (revision 0) +++ src/test/java/org/apache/hc/client5/http/testframework/TestHttpServerTest.java (revision 0) @@ -0,0 +1,106 @@ +/* + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.client5.http.testframework; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; + +import org.junit.Assert; + +public class TestHttpServerTest { + @Test + public void defaults() throws Exception { + final HttpServerTest test = new HttpServerTest(); + final Map request = test.initRequest(); + + Assert.assertNotNull("request should not be null", request); + Assert.assertEquals("Default method should be GET", "GET", request.get("method")); + + Assert.assertEquals("Default request body expected.", + HttpServerTestingFramework.DEFAULT_REQUEST_BODY, + request.get("body")); + + Assert.assertEquals("Default request content type expected.", + HttpServerTestingFramework.DEFAULT_REQUEST_CONTENT_TYPE, + request.get("contentType")); + + Assert.assertEquals("Default request query parameters expected.", + HttpServerTestingFramework.DEFAULT_REQUEST_QUERY, + request.get("query")); + + Assert.assertEquals("Default request headers expected.", + HttpServerTestingFramework.DEFAULT_REQUEST_HEADERS, + request.get("headers")); + + Assert.assertEquals("Default protocol version expected.", + HttpServerTestingFramework.DEFAULT_REQUEST_PROTOCOL_VERSION, + request.get("protocolVersion")); + + final Map responseExpectations = test.initResponseExpectations(); + Assert.assertNotNull("responseExpectations should not be null", responseExpectations); + Assert.assertEquals("Default status expected.", HttpServerTestingFramework.DEFAULT_RESPONSE_STATUS, + responseExpectations.get("status")); + + Assert.assertEquals("Default body expected.", HttpServerTestingFramework.DEFAULT_RESPONSE_BODY, + responseExpectations.get("body")); + + Assert.assertEquals("Default response content type expected.", HttpServerTestingFramework.DEFAULT_RESPONSE_CONTENT_TYPE, + responseExpectations.get("contentType")); + + Assert.assertEquals("Default headers expected.", HttpServerTestingFramework.DEFAULT_RESPONSE_HEADERS, + responseExpectations.get("headers")); + } + + @Test + public void changeStatus() throws Exception { + final Map testMap = new HashMap(); + final Map response = new HashMap(); + testMap.put("response", response); + response.put("status", 201); + + final HttpServerTest test = new HttpServerTest(testMap); + final Map responseExpectations = test.initResponseExpectations(); + + Assert.assertEquals("Status unexpected.", 201, responseExpectations.get("status")); + } + + @Test + public void changeMethod() throws Exception { + final Map testMap = new HashMap(); + final Map request = new HashMap(); + testMap.put("request", request); + request.put("method", "POST"); + + final HttpServerTest test = new HttpServerTest(testMap); + final Map requestExpectations = test.initRequest(); + + Assert.assertEquals("Method unexpected.", "POST", requestExpectations.get("method")); + } +} Index: src/test/java/org/apache/hc/client5/http/testframework/TestHttpServerTestingAdapter.java =================================================================== --- src/test/java/org/apache/hc/client5/http/testframework/TestHttpServerTestingAdapter.java (revision 0) +++ src/test/java/org/apache/hc/client5/http/testframework/TestHttpServerTestingAdapter.java (revision 0) @@ -0,0 +1,53 @@ +/* + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.hc.client5.http.testframework; + +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; + +public class TestHttpServerTestingAdapter { + + @Test + public void getHttpClientPOJOAdapter() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter(); + + final HttpClientPOJOAdapter pojoAdapter = adapter.getHttpClientPOJOAdapter(); + + Assert.assertNotNull("pojoAdapter should not be null", pojoAdapter); + } + + @Test + public void isRequestSupported() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter(); + + final Map request = null; + Assert.assertTrue("isRequestSupported should return true", adapter.isRequestSupported(request)); + + } +} Index: src/test/java/org/apache/hc/client5/http/testframework/TestHttpServerTestingFramework.java =================================================================== --- src/test/java/org/apache/hc/client5/http/testframework/TestHttpServerTestingFramework.java (revision 0) +++ src/test/java/org/apache/hc/client5/http/testframework/TestHttpServerTestingFramework.java (revision 0) @@ -0,0 +1,1151 @@ +/* + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.client5.http.testframework; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.hc.core5.http.HttpVersion; +import org.apache.hc.core5.http.ProtocolVersion; +import org.apache.hc.core5.http.entity.ContentType; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +public class TestHttpServerTestingFramework { + + @Test + public void ensureDefaultMapsUnmodifiable() throws Exception { + assertUnmodifiable(HttpServerTestingFramework.DEFAULT_REQUEST_QUERY); + assertUnmodifiable(HttpServerTestingFramework.DEFAULT_RESPONSE_HEADERS); + } + + private void assertUnmodifiable(final Map map) { + final String aKey = (String) map.keySet().toArray()[0]; + try { + map.remove(aKey); + Assert.fail("UnsupportedOperationException should have been thrown."); + } catch (UnsupportedOperationException e) { + // expected + } + } + + private HttpServerTestingFramework newWebServerTestingFramework(final HttpServerTestingAdapter adapter) + throws HttpServerTestingFrameworkException { + final HttpServerTestingFramework framework = new HttpServerTestingFramework(adapter); + // get rid of the default tests. + framework.deleteTests(); + + return framework; + } + + private HttpServerTestingFramework newWebServerTestingFramework() throws HttpServerTestingFrameworkException { + return newWebServerTestingFramework(null); // null adapter + } + + @Test + public void runTestsWithoutSettingAdapterThrows() throws Exception { + final HttpServerTestingFramework framework = newWebServerTestingFramework(); + try { + framework.runTests(); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @Test + public void nullAdapterThrows() throws Exception { + final HttpServerTestingAdapter adapter = null; + + final HttpServerTestingFramework framework = newWebServerTestingFramework(adapter); + try { + framework.runTests(); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @Test + public void nullSetAdapterThrows() throws Exception { + final HttpServerTestingAdapter adapter = null; + + final HttpServerTestingFramework framework = newWebServerTestingFramework(adapter); + framework.setAdapter(adapter); + try { + framework.runTests(); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @SuppressWarnings("unchecked") + @Test + public void goodAdapterWithConstructor() throws Exception { + final HttpServerTestingAdapter adapter = Mockito.mock(HttpServerTestingAdapter.class); + + // Have isRequestSupported() return false so no test will run. + Mockito.when(adapter.isRequestSupported(Mockito.anyMap())) + .thenReturn(false); + + final HttpServerTestingFramework framework = newWebServerTestingFramework(adapter); + + framework.runTests(); + + // since there are no tests, callMethod should not be called. + verifyCallMethodNeverCalled(adapter); + } + + @SuppressWarnings("unchecked") + private void verifyCallMethodNeverCalled(final HttpServerTestingAdapter adapter) throws Exception { + Mockito.verify(adapter, Mockito.never()).execute(Mockito.anyString(), Mockito.anyMap(), + Mockito.any(HttpServerTestingRequestHandler.class), Mockito.anyMap()); + } + + private HttpServerTestingFramework newFrameworkAndSetAdapter(final HttpServerTestingAdapter adapter) + throws HttpServerTestingFrameworkException { + final HttpServerTestingFramework framework = new HttpServerTestingFramework(); + framework.setAdapter(adapter); + + // get rid of the default tests. + framework.deleteTests(); + + return framework; + } + + @Test + public void goodAdapterWithSetter() throws Exception { + final HttpServerTestingAdapter adapter = Mockito.mock(HttpServerTestingAdapter.class); + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + framework.runTests(); + + // since there are no tests, callMethod should not be called. + verifyCallMethodNeverCalled(adapter); + } + + @Test + public void addTest() throws Exception { + final HttpServerTestingRequestHandler mockRequestHandler = Mockito.mock(HttpServerTestingRequestHandler.class); + + HttpServerTestingAdapter adapter = new HttpServerTestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) throws HttpServerTestingFrameworkException { + Assert.assertThat(defaultURI, matchesDefaultURI()); + + Assert.assertNotNull("request should not be null", request); + + // The request should be equal to the default request. + final Map defaultRequest = new HttpServerTest().initRequest(); + Assert.assertEquals("The request does not match the default", defaultRequest, request); + + Assert.assertSame("The request handler should have been passed to the adapter", + mockRequestHandler, requestHandler); + + // The responseExpectations should be equal to the default. + final Map defaultResponseExpectations = new HttpServerTest().initResponseExpectations(); + Assert.assertEquals("The responseExpectations do not match the defaults", + defaultResponseExpectations, responseExpectations); + + final Map response = new HashMap(); + response.put("status", responseExpectations.get("status")); + response.put("body", responseExpectations.get("body")); + response.put("contentType", responseExpectations.get("contentType")); + response.put("headers", responseExpectations.get("headers")); + return response; + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + framework.setRequestHandler(mockRequestHandler); + + framework.addTest(); + + framework.runTests(); + + // assertNothingThrown() should have been called. + Mockito.verify(mockRequestHandler).assertNothingThrown(); + } + + private Matcher matchesDefaultURI() { + final Matcher matcher = new BaseMatcher() { + private final String regex = "http://localhost:\\d+/"; + + @Override + public boolean matches(final Object o) { + return ((String) o).matches(regex); + } + + @Override + public void describeTo(final Description description) { + description.appendText("matches regex=" + regex); + } + }; + + return matcher; + } + + @Test + public void statusCheck() throws Exception { + HttpServerTestingAdapter adapter = new HttpServerTestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) { + + Assert.assertEquals(200, responseExpectations.get("status")); + + // return a different status than expected. + final Map response = new HashMap(); + response.put("status", 201); + return response; + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + framework.addTest(); + + try { + framework.runTests(); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + private Map alreadyCheckedResponse() { + // return an indication that the response has already been checked. + final Map response = new HashMap(); + response.put("status", HttpServerTestingFramework.ALREADY_CHECKED); + response.put("body", HttpServerTestingFramework.ALREADY_CHECKED); + response.put("contentType", HttpServerTestingFramework.ALREADY_CHECKED); + response.put("headers", HttpServerTestingFramework.ALREADY_CHECKED); + return response; + } + + @Test + public void responseAlreadyChecked() throws Exception { + final HttpServerTestingAdapter adapter = new HttpServerTestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) { + + return alreadyCheckedResponse(); + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + framework.addTest(); + + framework.runTests(); + } + + @Test + public void bodyCheck() throws Exception { + final HttpServerTestingAdapter adapter = new HttpServerTestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) { + + Assert.assertEquals(HttpServerTestingFramework.DEFAULT_RESPONSE_BODY, responseExpectations.get("body")); + + final Map response = new HashMap(); + response.put("status", HttpServerTestingFramework.ALREADY_CHECKED); + + // return a different body than expected. + response.put("body", HttpServerTestingFramework.DEFAULT_RESPONSE_BODY + "junk"); + return response; + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + framework.addTest(); + + try { + framework.runTests(); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @Test + public void responseContentTypeCheck() throws Exception { + final HttpServerTestingAdapter adapter = new HttpServerTestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) { + + Assert.assertEquals(HttpServerTestingFramework.DEFAULT_RESPONSE_CONTENT_TYPE, responseExpectations.get("contentType")); + + final Map response = new HashMap(); + response.put("status", HttpServerTestingFramework.ALREADY_CHECKED); + response.put("headers", HttpServerTestingFramework.ALREADY_CHECKED); + + // return the expected body + response.put("body", HttpServerTestingFramework.DEFAULT_RESPONSE_BODY); + // return a different content type than expected. + response.put("contentType", ContentType.DEFAULT_TEXT); + return response; + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + framework.addTest(); + + try { + framework.runTests(); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @Test + public void deepcopy() throws Exception { + // save a copy of the headers to make sure they haven't changed at the end of this test. + @SuppressWarnings("unchecked") + final Map headersCopy = (Map) HttpServerTestingFramework.deepcopy(HttpServerTestingFramework.DEFAULT_RESPONSE_HEADERS); + Assert.assertEquals(HttpServerTestingFramework.DEFAULT_RESPONSE_HEADERS, headersCopy); + + final Map deepMap = new HashMap(); + deepMap.put("headers", HttpServerTestingFramework.DEFAULT_RESPONSE_HEADERS); + + @SuppressWarnings("unchecked") + final Map deepMapCopy = (Map) HttpServerTestingFramework.deepcopy(deepMap); + Assert.assertEquals(deepMap, deepMapCopy); + + @SuppressWarnings("unchecked") + final Map headersMap = (Map) deepMapCopy.get("headers"); + Assert.assertEquals(headersCopy, headersMap); + + // now make sure the default headers have not changed for some unexpected reason. + Assert.assertEquals(HttpServerTestingFramework.DEFAULT_RESPONSE_HEADERS, headersCopy); + } + + @Test + public void removedHeaderCheck() throws Exception { + final HttpServerTestingAdapter adapter = new HttpServerTestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) { + + Assert.assertEquals(HttpServerTestingFramework.DEFAULT_RESPONSE_HEADERS, responseExpectations.get("headers")); + + @SuppressWarnings("unchecked") + final Map headersCopy = (Map) deepcopy(responseExpectations.get("headers")); + + // remove a header to force an error + final String headerName = (String) headersCopy.keySet().toArray()[0]; + headersCopy.remove(headerName); + + final Map response = new HashMap(); + response.put("status", HttpServerTestingFramework.ALREADY_CHECKED); + response.put("body", HttpServerTestingFramework.ALREADY_CHECKED); + + // return different headers than expected. + response.put("headers", headersCopy); + return response; + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + framework.addTest(); + + try { + framework.runTests(); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @Test + public void changedHeaderCheck() throws Exception { + final HttpServerTestingAdapter adapter = new HttpServerTestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) { + + Assert.assertEquals(HttpServerTestingFramework.DEFAULT_RESPONSE_HEADERS, responseExpectations.get("headers")); + + @SuppressWarnings("unchecked") + final Map headersCopy = (Map) deepcopy(responseExpectations.get("headers")); + + // change a header to force an error + final String headerName = (String) headersCopy.keySet().toArray()[0]; + headersCopy.put(headerName, headersCopy.get(headerName) + "junk"); + + final Map response = new HashMap(); + response.put("status", HttpServerTestingFramework.ALREADY_CHECKED); + response.put("body", HttpServerTestingFramework.ALREADY_CHECKED); + + // return different headers than expected. + response.put("headers", headersCopy); + return response; + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + framework.addTest(); + + try { + framework.runTests(); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + private Object deepcopy(final Object obj) { + try { + return HttpServerTestingFramework.deepcopy(obj); + } catch (Exception e) { + Assert.fail("deepcopy failed: " + e.getMessage()); + return null; + } + } + + @Test + public void requestMethodUnexpected() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) throws HttpServerTestingFrameworkException { + // change the request from what is expected. + Assert.assertTrue(request.get("method").equals("GET")); + request.put("method", "POST"); + return super.execute(defaultURI, request, requestHandler, responseExpectations); + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + final Map test = new HashMap(); + final Map request = new HashMap(); + test.put("request", request); + request.put("name", "MyName"); + + framework.addTest(test); + + try { + framework.runTests(); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + + // make sure the HTTP Client name is in the message. + final String message = e.getMessage(); + final String httpClientName = adapter.getHttpClientPOJOAdapter().getHTTPClientName(); + Assert.assertTrue( + "Message should contain httpClientName of " + httpClientName + "; message=" + message, + message.contains(httpClientName)); + + Assert.assertTrue( + "Message should contain the test. message=" + message, + message.contains("MyName")); + } + } + + @Test + public void status201() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter(); + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + final Map test = new HashMap(); + final Map response = new HashMap(); + test.put("response", response); + response.put("status", 201); + + framework.addTest(test); + + framework.runTests(); + } + + @Test + public void deepcopyOfTest() throws Exception { + final HttpServerTestingAdapter adapter = new HttpServerTestingAdapter() { + + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) + throws HttpServerTestingFrameworkException { + Assert.assertEquals(201, responseExpectations.get("status")); + return alreadyCheckedResponse(); + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + final Map test = new HashMap(); + final Map response = new HashMap(); + test.put("response", response); + response.put("status", 201); + + framework.addTest(test); + + // Make sure the framework makes a copy of the test for itself. + // This put should be ignored by the framework. + response.put("status", 300); + + framework.runTests(); + } + + @Test + public void removeParameter() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) throws HttpServerTestingFrameworkException { + // change the request from what is expected. + @SuppressWarnings("unchecked") + final Map query = (Map) request.get("query"); + Assert.assertTrue(query.containsKey("p1")); + query.remove("p1"); + return super.execute(defaultURI, request, requestHandler, responseExpectations); + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + framework.addTest(); + + try { + framework.runTests(); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @Test + public void changeParameter() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) throws HttpServerTestingFrameworkException { + // change the request from what is expected. + @SuppressWarnings("unchecked") + final Map query = (Map) request.get("query"); + Assert.assertTrue(query.containsKey("p1")); + query.put("p1", query.get("p1") + "junk"); + return super.execute(defaultURI, request, requestHandler, responseExpectations); + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + framework.addTest(); + + try { + framework.runTests(); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @Test + public void removeHeader() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) throws HttpServerTestingFrameworkException { + // change the request from what is expected. + @SuppressWarnings("unchecked") + final Map headers = (Map) request.get("headers"); + Assert.assertTrue(headers.containsKey("header1")); + headers.remove("header1"); + return super.execute(defaultURI, request, requestHandler, responseExpectations); + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + framework.addTest(); + + try { + framework.runTests(); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @Test + public void changeHeader() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) throws HttpServerTestingFrameworkException { + // change the request from what is expected. + @SuppressWarnings("unchecked") + final Map headers = (Map) request.get("headers"); + Assert.assertTrue(headers.containsKey("header1")); + headers.put("header1", headers.get("header1") + "junk"); + return super.execute(defaultURI, request, requestHandler, responseExpectations); + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + framework.addTest(); + + try { + framework.runTests(); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @Test + public void changeBody() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) throws HttpServerTestingFrameworkException { + // change the request from what is expected. + final String body = (String) request.get("body"); + Assert.assertNotNull(body); + request.put("body", request.get("body") + "junk"); + return super.execute(defaultURI, request, requestHandler, responseExpectations); + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + framework.addTest(); + + try { + framework.runTests(); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @Test + public void changeContentType() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) throws HttpServerTestingFrameworkException { + // change the request from what is expected. + final String contentType = (String) request.get("contentType"); + Assert.assertNotNull(contentType); + request.put("contentType", request.get("contentType") + "junk"); + return super.execute(defaultURI, request, requestHandler, responseExpectations); + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + framework.addTest(); + + try { + framework.runTests(); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @Test + public void changeProtocolVersion() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) throws HttpServerTestingFrameworkException { + // change the request from what is expected. + final ProtocolVersion protocolVersion = (ProtocolVersion) request.get("protocolVersion"); + Assert.assertNotNull(protocolVersion); + request.put("protocolVersion", HttpVersion.HTTP_1_0); + return super.execute(defaultURI, request, requestHandler, responseExpectations); + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + framework.addTest(); + + try { + framework.runTests(); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @Test + public void changeResponseExpectationsFails() throws Exception { + final HttpServerTestingAdapter adapter = new HttpServerTestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) throws HttpServerTestingFrameworkException { + + /* + * The adapter should change the responseExpectations in the modifyResponseExpectations() + * method before they are sent to the request handler. The expectations should not + * be changed here. + */ + // the next command should throw. + responseExpectations.put("status", 201); + return null; + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + framework.addTest(); + + try { + framework.runTests(); + Assert.fail("HttpServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + } + } + + @Test + public void changeResponseStatus() throws Exception { + HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) throws HttpServerTestingFrameworkException { + // change the responseExpectations from what is expected. The change should be ignored + // by the request handler, and a 200 should actually be returned. + Assert.assertEquals(200, responseExpectations.get("status")); + + // The next line is needed because we have to make a copy of the responseExpectations. + // It is an unmodifiable map. + final Map tempResponseExpectations = new HashMap(responseExpectations); + tempResponseExpectations.put("status", 201); + final Map response = super.execute(defaultURI, request, requestHandler, tempResponseExpectations); + Assert.assertEquals(200, response.get("status")); + + return response; + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + framework.addTest(); + + framework.runTests(); + } + + @Test + public void modifyRequestCalled() throws Exception { + final HttpServerTestingRequestHandler mockRequestHandler = Mockito.mock(HttpServerTestingRequestHandler.class); + final String UNLIKELY_ITEM = "something_unlikely_to_be_in_a_real_request"; + + final HttpServerTestingAdapter adapter = new HttpServerTestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) throws HttpServerTestingFrameworkException { + // make sure the modifyRequest method was called by seeing if the request was modified. + Assert.assertTrue("modifyRequest should have been called.", request.containsKey(UNLIKELY_ITEM)); + + final Map response = new HashMap(); + response.put("status", responseExpectations.get("status")); + response.put("body", responseExpectations.get("body")); + response.put("contentType", responseExpectations.get("contentType")); + response.put("headers", responseExpectations.get("headers")); + return response; + } + + @Override + public Map modifyRequest(final Map request) { + // let the adapter change the request if needed. + request.put(UNLIKELY_ITEM, new Object()); + return super.modifyRequest(request); + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + framework.setRequestHandler(mockRequestHandler); + + framework.addTest(); + + framework.runTests(); + + // assertNothingThrown() should have been called. + Mockito.verify(mockRequestHandler).assertNothingThrown(); + } + + @Test + public void modifyResponseExpectationsCalled() throws Exception { + final HttpServerTestingRequestHandler mockRequestHandler = Mockito.mock(HttpServerTestingRequestHandler.class); + final String UNLIKELY_ITEM = "something_unlikely_to_be_in_a_real_response"; + + final HttpServerTestingAdapter adapter = new HttpServerTestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) throws HttpServerTestingFrameworkException { + // make sure the modifyRequest method was called by seeing if the request was modified. + Assert.assertTrue("modifyResponseExpectations should have been called.", responseExpectations.containsKey(UNLIKELY_ITEM)); + + final Map response = new HashMap(); + response.put("status", responseExpectations.get("status")); + response.put("body", responseExpectations.get("body")); + response.put("contentType", responseExpectations.get("contentType")); + response.put("headers", responseExpectations.get("headers")); + return response; + } + + @Override + public Map modifyResponseExpectations( + final Map request, + final Map responseExpectations) { + // let the adapter change the request if needed. + responseExpectations.put(UNLIKELY_ITEM, new Object()); + return super.modifyResponseExpectations(request, responseExpectations); + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + framework.setRequestHandler(mockRequestHandler); + + framework.addTest(); + + framework.runTests(); + + // assertNothingThrown() should have been called. + Mockito.verify(mockRequestHandler).assertNothingThrown(); + } + + @Test + public void adapterDoesNotSupport() throws Exception { + + HttpServerTestingAdapter adapter = new HttpServerTestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) throws HttpServerTestingFrameworkException { + + Assert.fail("callMethod should not have been called"); + return null; + } + + @Override + public boolean isRequestSupported(final Map request) { + return false; + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + framework.addTest(); + + framework.runTests(); + } + + @Test + public void defaultTestsWithMockedAdapter() throws Exception { + final Set calledMethodSet = new HashSet(); + + final HttpServerTestingAdapter adapter = new HttpServerTestingAdapter() { + @Override + public Map execute( + final String defaultURI, + final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) throws HttpServerTestingFrameworkException { + + calledMethodSet.add((String) request.get("method")); + return alreadyCheckedResponse(); + } + }; + + // create the framework without deleting the default tests. + final HttpServerTestingFramework framework = new HttpServerTestingFramework(); + framework.setAdapter(adapter); + + framework.runTests(); + + for (String method : HttpServerTestingFramework.ALL_METHODS) { + Assert.assertTrue("Method not in default tests. method=" + method, calledMethodSet.contains(method)); + } + } + + @Test + public void defaultTests() throws Exception { + final HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter(); + + // create the framework without deleting the default tests. + final HttpServerTestingFramework framework = new HttpServerTestingFramework(); + framework.setAdapter(adapter); + + framework.runTests(); + + } + + @Test + public void addTestNoMocks() throws HttpServerTestingFrameworkException { + + final HttpServerTestingFramework framework = new HttpServerTestingFramework(new HttpClient5TestingAdapter()); + +// The following test could be constructed in Groovy/Spock like this: +// +// HttpServerTestingFramework.ALL_METHODS.each { method -> +// framework.addTest( +// request: [ +// path: '/stuff', +// method:method, +// query: [param : 'something'], +// headers: [header1:'stuff', header2:'more-stuff'], +// contentType: 'text/plain; charset=us-ascii', +// body: 'What is the meaning of life?', +// ], +// response: [ +// status:201, +// headers: [header3:'header_stuff',], +// contentType: 'text/html; charset=us-ascii', +// body: responseBody, +// ], +// ) + + final Map test = new HashMap(); + + // Add request. + final Map request = new HashMap(); + test.put("request", request); + + request.put("path", "/stuff"); + + final Map queryMap = new HashMap(); + request.put("query", queryMap); + + queryMap.put("param", "something"); + + final Map requestHeadersMap = new HashMap(); + request.put("headers", requestHeadersMap); + + requestHeadersMap.put("header1", "stuff"); + requestHeadersMap.put("header2", "more-stuff"); + + request.put("contentType", "text/plain; charset=us-ascii"); + request.put("body", "What is the meaning of life?"); + + // Response + final Map response = new HashMap(); + test.put("response", response); + + response.put("status", 201); + + final Map responseHeadersMap = new HashMap(); + response.put("headers", responseHeadersMap); + + responseHeadersMap.put("header3", "header_stuff"); + + response.put("contentType", "text/html; charset=us-ascii"); + response.put("body", "42"); + + for (String method : HttpServerTestingFramework.ALL_METHODS) { + request.put("method", method); + + framework.addTest(test); + } + framework.runTests(); + } + + @Test + public void nulls() throws HttpServerTestingFrameworkException { + + final HttpServerTestingFramework framework = new HttpServerTestingFramework(new HttpClient5TestingAdapter()); + +// The following test could be constructed in Groovy/Spock like this: +// +// WebServerTestingFramework.ALL_METHODS.each { method -> +// framework.addTest( +// request: [ +// path: null, +// method:method, +// query: null, +// headers: null, +// contentType: null, +// body: null, +// ], +// response: [ +// status:null, +// headers: null, +// contentType: null, +// body: null, +// ], +// ) + + final Map test = new HashMap(); + + // Add request. + final Map request = new HashMap(); + test.put("request", request); + + request.put("path", null); + + request.put("query", null); + + + request.put("headers", null); + + request.put("contentType", null); + request.put("body", null); + + // Response + final Map response = new HashMap(); + test.put("response", response); + + response.put("status", null); + + response.put("headers", null); + + response.put("contentType", null); + response.put("body", null); + + for (String method : HttpServerTestingFramework.ALL_METHODS) { + request.put("method", method); + + framework.addTest(test); + } + framework.runTests(); + } + + @Test + public void parameterInPath() throws Exception { + HttpServerTestingAdapter adapter = new HttpClient5TestingAdapter() { + @Override + public Map execute(final String defaultURI, final Map request, + final HttpServerTestingRequestHandler requestHandler, + final Map responseExpectations) throws HttpServerTestingFrameworkException { + @SuppressWarnings("unchecked") + final Map query = (Map) request.get("query"); + Assert.assertTrue("Parameters appended to the path should have been put in the query.", + query.containsKey("stuffParm")); + + Assert.assertTrue(query.containsKey("stuffParm2")); + Assert.assertEquals("stuff", query.get("stuffParm")); + Assert.assertEquals("stuff2", query.get("stuffParm2")); + + Assert.assertEquals("/stuff", request.get("path")); + return alreadyCheckedResponse(); + } + }; + + final HttpServerTestingFramework framework = newFrameworkAndSetAdapter(adapter); + + final Map test = new HashMap(); + + // Add request. + final Map request = new HashMap(); + test.put("request", request); + + request.put("path", "/stuff?stuffParm=stuff&stuffParm2=stuff2"); + + framework.addTest(test); + + framework.runTests(); + } + +} \ No newline at end of file Index: src/test/java/org/apache/hc/client5/http/testframework/TestHttpServerTestingRequestHandler.java =================================================================== --- src/test/java/org/apache/hc/client5/http/testframework/TestHttpServerTestingRequestHandler.java (revision 0) +++ src/test/java/org/apache/hc/client5/http/testframework/TestHttpServerTestingRequestHandler.java (revision 0) @@ -0,0 +1,79 @@ +/* + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.client5.http.testframework; + +import java.io.IOException; + +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.junit.Assert; +import org.junit.Test; + +public class TestHttpServerTestingRequestHandler { + @Test + public void assertNothingThrown() throws Exception { + final HttpServerTestingRequestHandler handler = new HttpServerTestingRequestHandler() { + + @Override + public void handle(final HttpRequest request, final HttpResponse response, final HttpContext context) + throws HttpException, IOException { + } + }; + + handler.assertNothingThrown(); + } + + @Test + public void assertNothingThrownThrows() throws Exception { + final String errorMessage = "thrown intentionally"; + + final HttpServerTestingRequestHandler handler = new HttpServerTestingRequestHandler() { + + @Override + public void handle(final HttpRequest request, final HttpResponse response, final HttpContext context) + throws HttpException, IOException { + thrown = new HttpServerTestingFrameworkException(errorMessage); + } + }; + + handler.handle(null, null, null); + try { + handler.assertNothingThrown(); + Assert.fail("WebServerTestingFrameworkException should have been thrown"); + } catch (HttpServerTestingFrameworkException e) { + // expected + Assert.assertEquals("Unexpected message", errorMessage, e.getMessage()); + } + + // a second call should not throw + handler.assertNothingThrown(); + } + +}