HTTPCLIENT-1244: Replaced EasyMock with Mockito in the HTTP cache unit tests

This commit is contained in:
Oleg Kalnichevski 2021-06-12 21:57:44 +02:00
parent d77112f608
commit 760795b6df
15 changed files with 1270 additions and 2978 deletions

View File

@ -27,14 +27,10 @@ addons:
- maven - maven
jdk: jdk:
- oraclejdk8
- openjdk12 - openjdk12
- oraclejdk12 - oraclejdk16
- openjdk-ea - openjdk-ea
matrix: matrix:
allow_failures: allow_failures:
- jdk: openjdk-ea - jdk: openjdk-ea
after_success:
- mvn clean cobertura:cobertura coveralls:report

View File

@ -79,11 +79,6 @@
<artifactId>mockito-core</artifactId> <artifactId>mockito-core</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.apache.httpcomponents.client5</groupId> <groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId> <artifactId>httpclient5</artifactId>

View File

@ -1,186 +0,0 @@
/*
* ====================================================================
* 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
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.cache;
import java.io.IOException;
import java.util.HashMap;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.classic.ExecChain;
import org.apache.hc.client5.http.classic.ExecChainHandler;
import org.apache.hc.client5.http.classic.ExecRuntime;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpEntity;
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.io.support.ClassicRequestBuilder;
import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
import org.easymock.EasyMock;
import org.easymock.IExpectationSetters;
import org.junit.Before;
public abstract class AbstractProtocolTest {
protected static final int MAX_BYTES = 1024;
protected static final int MAX_ENTRIES = 100;
protected final int entityLength = 128;
protected HttpHost host;
protected HttpRoute route;
protected HttpEntity body;
protected HttpClientContext context;
protected ExecChain mockExecChain;
protected ExecRuntime mockExecRuntime;
protected HttpCache mockCache;
protected ClassicHttpRequest request;
protected ClassicHttpResponse originResponse;
protected CacheConfig config;
protected ExecChainHandler impl;
protected HttpCache cache;
public static ClassicHttpRequest eqRequest(final ClassicHttpRequest in) {
EasyMock.reportMatcher(new RequestEquivalent(in));
return null;
}
public static HttpResponse eqResponse(final HttpResponse in) {
EasyMock.reportMatcher(new ResponseEquivalent(in));
return null;
}
public static ClassicHttpResponse eqCloseableResponse(final ClassicHttpResponse in) {
EasyMock.reportMatcher(new ResponseEquivalent(in));
return null;
}
@Before
public void setUp() {
host = new HttpHost("foo.example.com", 80);
route = new HttpRoute(host);
body = HttpTestUtils.makeBody(entityLength);
request = new BasicClassicHttpRequest("GET", "/foo");
context = HttpClientContext.create();
originResponse = HttpTestUtils.make200Response();
config = CacheConfig.custom()
.setMaxCacheEntries(MAX_ENTRIES)
.setMaxObjectSize(MAX_BYTES)
.build();
cache = new BasicHttpCache(config);
mockExecChain = EasyMock.createNiceMock(ExecChain.class);
mockExecRuntime = EasyMock.createNiceMock(ExecRuntime.class);
mockCache = EasyMock.createNiceMock(HttpCache.class);
impl = createCachingExecChain(cache, config);
}
public ClassicHttpResponse execute(final ClassicHttpRequest request) throws IOException, HttpException {
return impl.execute(
ClassicRequestBuilder.copy(request).build(),
new ExecChain.Scope("test", route, request, mockExecRuntime, context),
mockExecChain);
}
protected ExecChainHandler createCachingExecChain(final HttpCache cache, final CacheConfig config) {
return new CachingExec(cache, null, config);
}
protected boolean supportsRangeAndContentRangeHeaders(final ExecChainHandler impl) {
return impl instanceof CachingExec && ((CachingExec) impl).supportsRangeAndContentRangeHeaders();
}
protected void replayMocks() {
EasyMock.replay(mockExecChain);
EasyMock.replay(mockCache);
}
protected void verifyMocks() {
EasyMock.verify(mockExecChain);
EasyMock.verify(mockCache);
}
protected IExpectationSetters<ClassicHttpResponse> backendExpectsAnyRequest() throws Exception {
final ClassicHttpResponse resp = mockExecChain.proceed(
EasyMock.isA(ClassicHttpRequest.class),
EasyMock.isA(ExecChain.Scope.class));
return EasyMock.expect(resp);
}
protected IExpectationSetters<ClassicHttpResponse> backendExpectsAnyRequestAndReturn(
final ClassicHttpResponse response) throws Exception {
final ClassicHttpResponse resp = mockExecChain.proceed(
EasyMock.isA(ClassicHttpRequest.class),
EasyMock.isA(ExecChain.Scope.class));
return EasyMock.expect(resp).andReturn(response);
}
protected void emptyMockCacheExpectsNoPuts() throws Exception {
mockExecChain = EasyMock.createNiceMock(ExecChain.class);
mockCache = EasyMock.createNiceMock(HttpCache.class);
impl = new CachingExec(mockCache, null, config);
EasyMock.expect(mockCache.getCacheEntry(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class)))
.andReturn(null).anyTimes();
EasyMock.expect(mockCache.getVariantCacheEntriesWithEtags(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class)))
.andReturn(new HashMap<>()).anyTimes();
mockCache.flushCacheEntriesFor(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class));
EasyMock.expectLastCall().anyTimes();
mockCache.flushCacheEntriesFor(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class));
EasyMock.expectLastCall().anyTimes();
mockCache.flushCacheEntriesInvalidatedByRequest(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class));
EasyMock.expectLastCall().anyTimes();
mockCache.flushCacheEntriesInvalidatedByExchange(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class), EasyMock.isA(HttpResponse.class));
EasyMock.expectLastCall().anyTimes();
}
protected void behaveAsNonSharedCache() {
config = CacheConfig.custom()
.setMaxCacheEntries(MAX_ENTRIES)
.setMaxObjectSize(MAX_BYTES)
.setSharedCache(false)
.build();
impl = new CachingExec(cache, null, config);
}
public AbstractProtocolTest() {
super();
}
}

View File

@ -1,65 +0,0 @@
/*
* ====================================================================
* 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
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.cache;
import java.io.IOException;
import org.apache.hc.client5.http.classic.ExecChain;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
public class DummyBackend implements ExecChain {
private ClassicHttpRequest request;
private ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
private int executions;
public void setResponse(final ClassicHttpResponse resp) {
response = resp;
}
public HttpRequest getCapturedRequest() {
return request;
}
@Override
public ClassicHttpResponse proceed(
final ClassicHttpRequest request,
final Scope scope) throws IOException, HttpException {
this.request = request;
executions++;
return response;
}
public int getExecutions() {
return executions;
}
}

View File

@ -251,7 +251,7 @@ public class HttpTestUtils {
} }
public static HttpCacheEntry makeCacheEntry(final Date requestDate, public static HttpCacheEntry makeCacheEntry(final Date requestDate,
final Date responseDate, final Header[] headers) { final Date responseDate, final Header... headers) {
final byte[] bytes = getRandomBytes(128); final byte[] bytes = getRandomBytes(128);
return makeCacheEntry(requestDate, responseDate, headers, bytes); return makeCacheEntry(requestDate, responseDate, headers, bytes);
} }
@ -283,7 +283,7 @@ public class HttpTestUtils {
return makeCacheEntry(getStockHeaders(new Date()), bytes); return makeCacheEntry(getStockHeaders(new Date()), bytes);
} }
public static HttpCacheEntry makeCacheEntry(final Header[] headers) { public static HttpCacheEntry makeCacheEntry(final Header... headers) {
return makeCacheEntry(headers, getRandomBytes(128)); return makeCacheEntry(headers, getRandomBytes(128));
} }
@ -292,27 +292,27 @@ public class HttpTestUtils {
return makeCacheEntry(now, now); return makeCacheEntry(now, now);
} }
public static HttpCacheEntry makeCacheEntryWithNoRequestMethodOrEntity(final Header[] headers) { public static HttpCacheEntry makeCacheEntryWithNoRequestMethodOrEntity(final Header... headers) {
final Date now = new Date(); final Date now = new Date();
return new HttpCacheEntry(now, now, HttpStatus.SC_OK, headers, null, null); return new HttpCacheEntry(now, now, HttpStatus.SC_OK, headers, null, null);
} }
public static HttpCacheEntry makeCacheEntryWithNoRequestMethod(final Header[] headers) { public static HttpCacheEntry makeCacheEntryWithNoRequestMethod(final Header... headers) {
final Date now = new Date(); final Date now = new Date();
return new HttpCacheEntry(now, now, HttpStatus.SC_OK, headers, new HeapResource(getRandomBytes(128)), null); return new HttpCacheEntry(now, now, HttpStatus.SC_OK, headers, new HeapResource(getRandomBytes(128)), null);
} }
public static HttpCacheEntry make204CacheEntryWithNoRequestMethod(final Header[] headers) { public static HttpCacheEntry make204CacheEntryWithNoRequestMethod(final Header... headers) {
final Date now = new Date(); final Date now = new Date();
return new HttpCacheEntry(now, now, HttpStatus.SC_NO_CONTENT, headers, null, null); return new HttpCacheEntry(now, now, HttpStatus.SC_NO_CONTENT, headers, null, null);
} }
public static HttpCacheEntry makeHeadCacheEntry(final Header[] headers) { public static HttpCacheEntry makeHeadCacheEntry(final Header... headers) {
final Date now = new Date(); final Date now = new Date();
return new HttpCacheEntry(now, now, HttpStatus.SC_OK, headers, null, null); return new HttpCacheEntry(now, now, HttpStatus.SC_OK, headers, null, null);
} }
public static HttpCacheEntry makeHeadCacheEntryWithNoRequestMethod(final Header[] headers) { public static HttpCacheEntry makeHeadCacheEntryWithNoRequestMethod(final Header... headers) {
final Date now = new Date(); final Date now = new Date();
return new HttpCacheEntry(now, now, HttpStatus.SC_OK, headers, null, null); return new HttpCacheEntry(now, now, HttpStatus.SC_OK, headers, null, null);
} }

View File

@ -27,30 +27,27 @@
package org.apache.hc.client5.http.impl.cache; package org.apache.hc.client5.http.impl.cache;
import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequest;
import org.easymock.IArgumentMatcher; import org.mockito.ArgumentMatcher;
import org.mockito.ArgumentMatchers;
public class RequestEquivalent implements IArgumentMatcher { public class RequestEquivalent<T extends HttpRequest> implements ArgumentMatcher<T> {
private final HttpRequest expected; private final T expected;
public RequestEquivalent(final HttpRequest expected) { public RequestEquivalent(final T expected) {
this.expected = expected; this.expected = expected;
} }
@Override @Override
public boolean matches(final Object actual) { public boolean matches(final T argument) {
if (!(actual instanceof HttpRequest)) { if (argument == null) {
return false; return false;
} }
final HttpRequest other = (HttpRequest) actual; return HttpTestUtils.equivalent(expected, argument);
return HttpTestUtils.equivalent(expected, other);
} }
@Override public static <T extends HttpRequest> T eq(final T request) {
public void appendTo(final StringBuffer buf) { return ArgumentMatchers.argThat(new RequestEquivalent<>(request));
buf.append("eqRequest(");
buf.append(expected);
buf.append(")");
} }
} }

View File

@ -27,30 +27,27 @@
package org.apache.hc.client5.http.impl.cache; package org.apache.hc.client5.http.impl.cache;
import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpResponse;
import org.easymock.IArgumentMatcher; import org.mockito.ArgumentMatcher;
import org.mockito.ArgumentMatchers;
public class ResponseEquivalent implements IArgumentMatcher { public class ResponseEquivalent<T extends HttpResponse> implements ArgumentMatcher<T> {
private final HttpResponse expected; private final T expected;
public ResponseEquivalent(final HttpResponse expected) { public ResponseEquivalent(final T expected) {
this.expected = expected; this.expected = expected;
} }
@Override @Override
public boolean matches(final Object actual) { public boolean matches(final T argument) {
if (!(actual instanceof HttpResponse)) { if (argument == null) {
return false; return false;
} }
final HttpResponse other = (HttpResponse) actual; return HttpTestUtils.equivalent(expected, argument);
return HttpTestUtils.equivalent(expected, other);
} }
@Override public static <T extends HttpResponse> T eq(final T response) {
public void appendTo(final StringBuffer buf) { return ArgumentMatchers.argThat(new ResponseEquivalent<>(response));
buf.append("eqRequest(");
buf.append(expected);
buf.append(")");
} }
} }

View File

@ -1,458 +0,0 @@
/*
* ====================================================================
* 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
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.cache;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.isA;
import static org.easymock.EasyMock.same;
import static org.easymock.EasyMock.createMockBuilder;
import static org.easymock.EasyMock.createNiceMock;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.classic.ExecChain;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HeaderElements;
import org.apache.hc.core5.http.HttpHeaders;
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.HttpStatus;
import org.apache.hc.core5.http.HttpVersion;
import org.apache.hc.core5.http.io.entity.InputStreamEntity;
import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
import org.easymock.EasyMock;
import org.easymock.IExpectationSetters;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@SuppressWarnings("boxing") // test code
public class TestCachingExec extends TestCachingExecChain {
private static final String GET_CURRENT_DATE = "getCurrentDate";
private static final String HANDLE_BACKEND_RESPONSE = "handleBackendResponse";
private static final String CALL_BACKEND = "callBackend";
private static final String REVALIDATE_CACHE_ENTRY = "revalidateCacheEntry";
private CachingExec impl;
private boolean mockedImpl;
private ExecChain.Scope scope;
private ClassicHttpResponse mockBackendResponse;
private Date requestDate;
private Date responseDate;
@Override
@Before
public void setUp() {
super.setUp();
scope = new ExecChain.Scope("test", route, request, mockEndpoint, context);
mockBackendResponse = createNiceMock(ClassicHttpResponse.class);
requestDate = new Date(System.currentTimeMillis() - 1000);
responseDate = new Date();
}
@Override
public CachingExec createCachingExecChain(
final HttpCache mockCache, final CacheValidityPolicy mockValidityPolicy,
final ResponseCachingPolicy mockResponsePolicy,
final CachedHttpResponseGenerator mockResponseGenerator,
final CacheableRequestPolicy mockRequestPolicy,
final CachedResponseSuitabilityChecker mockSuitabilityChecker,
final ResponseProtocolCompliance mockResponseProtocolCompliance,
final RequestProtocolCompliance mockRequestProtocolCompliance,
final DefaultCacheRevalidator mockCacheRevalidator,
final ConditionalRequestBuilder<ClassicHttpRequest> mockConditionalRequestBuilder,
final CacheConfig config) {
return impl = new CachingExec(
mockCache,
mockValidityPolicy,
mockResponsePolicy,
mockResponseGenerator,
mockRequestPolicy,
mockSuitabilityChecker,
mockResponseProtocolCompliance,
mockRequestProtocolCompliance,
mockCacheRevalidator,
mockConditionalRequestBuilder,
config);
}
@Override
public CachingExec createCachingExecChain(final HttpCache cache, final CacheConfig config) {
return impl = new CachingExec(cache, null, config);
}
@Override
protected void replayMocks() {
super.replayMocks();
replay(mockBackendResponse);
if (mockedImpl) {
replay(impl);
}
}
@Override
protected void verifyMocks() {
super.verifyMocks();
verify(mockBackendResponse);
if (mockedImpl) {
verify(impl);
}
}
@Test
public void testRequestThatCannotBeServedFromCacheCausesBackendRequest() throws Exception {
cacheInvalidatorWasCalled();
requestPolicyAllowsCaching(false);
mockImplMethods(CALL_BACKEND);
implExpectsAnyRequestAndReturn(mockBackendResponse);
requestIsFatallyNonCompliant(null);
replayMocks();
final HttpResponse result = impl.execute(request, scope, mockExecChain);
verifyMocks();
Assert.assertSame(mockBackendResponse, result);
}
@Test
public void testCacheMissCausesBackendRequest() throws Exception {
mockImplMethods(CALL_BACKEND);
requestPolicyAllowsCaching(true);
getCacheEntryReturns(null);
getVariantCacheEntriesReturns(new HashMap<>());
requestIsFatallyNonCompliant(null);
implExpectsAnyRequestAndReturn(mockBackendResponse);
replayMocks();
final HttpResponse result = impl.execute(request, scope, mockExecChain);
verifyMocks();
Assert.assertSame(mockBackendResponse, result);
Assert.assertEquals(1, impl.getCacheMisses());
Assert.assertEquals(0, impl.getCacheHits());
Assert.assertEquals(0, impl.getCacheUpdates());
}
@Test
public void testUnsuitableUnvalidatableCacheEntryCausesBackendRequest() throws Exception {
mockImplMethods(CALL_BACKEND);
requestPolicyAllowsCaching(true);
requestIsFatallyNonCompliant(null);
getCacheEntryReturns(mockCacheEntry);
cacheEntrySuitable(false);
cacheEntryValidatable(false);
expect(mockConditionalRequestBuilder.buildConditionalRequest(request, mockCacheEntry))
.andReturn(request);
backendExpectsRequestAndReturn(request, mockBackendResponse);
expect(mockBackendResponse.getVersion()).andReturn(HttpVersion.HTTP_1_1).anyTimes();
expect(mockBackendResponse.getCode()).andReturn(200);
replayMocks();
final HttpResponse result = impl.execute(request, scope, mockExecChain);
verifyMocks();
Assert.assertSame(mockBackendResponse, result);
Assert.assertEquals(0, impl.getCacheMisses());
Assert.assertEquals(1, impl.getCacheHits());
Assert.assertEquals(1, impl.getCacheUpdates());
}
@Test
public void testUnsuitableValidatableCacheEntryCausesRevalidation() throws Exception {
mockImplMethods(REVALIDATE_CACHE_ENTRY);
requestPolicyAllowsCaching(true);
requestIsFatallyNonCompliant(null);
getCacheEntryReturns(mockCacheEntry);
cacheEntrySuitable(false);
cacheEntryValidatable(true);
cacheEntryMustRevalidate(false);
cacheEntryProxyRevalidate(false);
mayReturnStaleWhileRevalidating(false);
expect(impl.revalidateCacheEntry(
isA(HttpHost.class),
isA(ClassicHttpRequest.class),
isA(ExecChain.Scope.class),
isA(ExecChain.class),
isA(HttpCacheEntry.class))).andReturn(mockBackendResponse);
replayMocks();
final HttpResponse result = impl.execute(request, scope, mockExecChain);
verifyMocks();
Assert.assertSame(mockBackendResponse, result);
Assert.assertEquals(0, impl.getCacheMisses());
Assert.assertEquals(1, impl.getCacheHits());
Assert.assertEquals(0, impl.getCacheUpdates());
}
@Test
public void testRevalidationCallsHandleBackEndResponseWhenNot200Or304() throws Exception {
mockImplMethods(GET_CURRENT_DATE, HANDLE_BACKEND_RESPONSE);
final ClassicHttpRequest validate = new BasicClassicHttpRequest("GET", "/");
final ClassicHttpResponse originResponse = new BasicClassicHttpResponse(HttpStatus.SC_NOT_FOUND, "Not Found");
final ClassicHttpResponse finalResponse = HttpTestUtils.make200Response();
conditionalRequestBuilderReturns(validate);
getCurrentDateReturns(requestDate);
backendExpectsRequestAndReturn(validate, originResponse);
getCurrentDateReturns(responseDate);
expect(impl.handleBackendResponse(
same(host),
same(validate),
same(scope),
eq(requestDate),
eq(responseDate),
same(originResponse))).andReturn(finalResponse);
replayMocks();
final HttpResponse result =
impl.revalidateCacheEntry(host, request, scope, mockExecChain, entry);
verifyMocks();
Assert.assertSame(finalResponse, result);
}
@Test
public void testRevalidationUpdatesCacheEntryAndPutsItToCacheWhen304ReturningCachedResponse()
throws Exception {
mockImplMethods(GET_CURRENT_DATE);
final ClassicHttpRequest validate = new BasicClassicHttpRequest("GET", "/");
final ClassicHttpResponse originResponse = HttpTestUtils.make304Response();
final HttpCacheEntry updatedEntry = HttpTestUtils.makeCacheEntry();
conditionalRequestBuilderReturns(validate);
getCurrentDateReturns(requestDate);
backendExpectsRequestAndReturn(validate, originResponse);
getCurrentDateReturns(responseDate);
expect(mockCache.updateCacheEntry(
eq(host),
same(request),
same(entry),
same(originResponse),
eq(requestDate),
eq(responseDate)))
.andReturn(updatedEntry);
expect(mockSuitabilityChecker.isConditional(request)).andReturn(false);
responseIsGeneratedFromCache(SimpleHttpResponse.create(HttpStatus.SC_OK));
replayMocks();
impl.revalidateCacheEntry(host, request, scope, mockExecChain, entry);
verifyMocks();
}
@Test
public void testRevalidationRewritesAbsoluteUri() throws Exception {
mockImplMethods(GET_CURRENT_DATE);
// Fail on an unexpected request, rather than causing a later NPE
EasyMock.resetToStrict(mockExecChain);
final ClassicHttpRequest validate = new HttpGet("http://foo.example.com/resource");
final ClassicHttpRequest relativeValidate = new BasicClassicHttpRequest("GET", "/resource");
final ClassicHttpResponse originResponse = new BasicClassicHttpResponse(HttpStatus.SC_OK, "Okay");
conditionalRequestBuilderReturns(validate);
getCurrentDateReturns(requestDate);
final ClassicHttpResponse resp = mockExecChain.proceed(
eqRequest(relativeValidate), isA(ExecChain.Scope.class));
expect(resp).andReturn(originResponse);
getCurrentDateReturns(responseDate);
replayMocks();
impl.revalidateCacheEntry(host, request, scope, mockExecChain, entry);
verifyMocks();
}
@Test
public void testEndlessResponsesArePassedThrough() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
resp1.setHeader("Date", DateUtils.formatDate(new Date()));
resp1.setHeader("Server", "MockOrigin/1.0");
resp1.setHeader(HttpHeaders.TRANSFER_ENCODING, HeaderElements.CHUNKED_ENCODING);
final AtomicInteger size = new AtomicInteger();
final AtomicInteger maxlength = new AtomicInteger(Integer.MAX_VALUE);
resp1.setEntity(new InputStreamEntity(new InputStream() {
private Throwable closed;
@Override
public void close() throws IOException {
closed = new Throwable();
super.close();
}
@Override
public int read() throws IOException {
Thread.yield();
if (closed != null) {
throw new IOException("Response has been closed");
}
if (size.incrementAndGet() > maxlength.get()) {
return -1;
}
return 'y';
}
}, -1, null));
final ClassicHttpResponse resp = mockExecChain.proceed(
isA(ClassicHttpRequest.class), isA(ExecChain.Scope.class));
EasyMock.expect(resp).andReturn(resp1);
final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
replayMocks();
final ClassicHttpResponse resp2 = impl.execute(req1, scope, mockExecChain);
maxlength.set(size.get() * 2);
verifyMocks();
assertTrue(HttpTestUtils.semanticallyTransparent(resp1, resp2));
}
@Test
public void testCallBackendMakesBackEndRequestAndHandlesResponse() throws Exception {
mockImplMethods(GET_CURRENT_DATE, HANDLE_BACKEND_RESPONSE);
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
getCurrentDateReturns(requestDate);
backendExpectsRequestAndReturn(request, resp);
getCurrentDateReturns(responseDate);
handleBackendResponseReturnsResponse(request, resp);
replayMocks();
impl.callBackend(host, request, scope, mockExecChain);
verifyMocks();
}
@Test
public void testDoesNotFlushCachesOnCacheHit() throws Exception {
requestPolicyAllowsCaching(true);
requestIsFatallyNonCompliant(null);
getCacheEntryReturns(mockCacheEntry);
doesNotFlushCache();
cacheEntrySuitable(true);
cacheEntryValidatable(true);
responseIsGeneratedFromCache(SimpleHttpResponse.create(HttpStatus.SC_OK));
replayMocks();
impl.execute(request, scope, mockExecChain);
verifyMocks();
}
private IExpectationSetters<ClassicHttpResponse> implExpectsAnyRequestAndReturn(
final ClassicHttpResponse response) throws Exception {
final ClassicHttpResponse resp = impl.callBackend(
same(host),
isA(ClassicHttpRequest.class),
isA(ExecChain.Scope.class),
isA(ExecChain.class));
return EasyMock.expect(resp).andReturn(response);
}
private void getVariantCacheEntriesReturns(final Map<String,Variant> result) {
expect(mockCache.getVariantCacheEntriesWithEtags(host, request)).andReturn(result);
}
private void cacheInvalidatorWasCalled() {
mockCache.flushCacheEntriesInvalidatedByRequest((HttpHost)anyObject(), (HttpRequest)anyObject());
}
private void getCurrentDateReturns(final Date date) {
expect(impl.getCurrentDate()).andReturn(date);
}
private void handleBackendResponseReturnsResponse(final ClassicHttpRequest request, final ClassicHttpResponse response)
throws IOException {
expect(
impl.handleBackendResponse(
same(host),
same(request),
same(scope),
isA(Date.class),
isA(Date.class),
isA(ClassicHttpResponse.class))).andReturn(response);
}
private void mockImplMethods(final String... methods) {
mockedImpl = true;
impl = createMockBuilder(CachingExec.class).withConstructor(
mockCache,
mockValidityPolicy,
mockResponsePolicy,
mockResponseGenerator,
mockRequestPolicy,
mockSuitabilityChecker,
mockResponseProtocolCompliance,
mockRequestProtocolCompliance,
mockCacheRevalidator,
mockConditionalRequestBuilder,
config).addMockedMethods(methods).createNiceMock();
}
}

View File

@ -26,26 +26,90 @@
*/ */
package org.apache.hc.client5.http.impl.cache; package org.apache.hc.client5.http.impl.cache;
import java.io.IOException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.util.Date; import java.util.Date;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.classic.ExecChain;
import org.apache.hc.client5.http.classic.ExecRuntime;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.utils.DateUtils; import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
/** /**
* This class tests behavior that is allowed (MAY) by the HTTP/1.1 protocol * This class tests behavior that is allowed (MAY) by the HTTP/1.1 protocol
* specification and for which we have implemented the behavior in HTTP cache. * specification and for which we have implemented the behavior in HTTP cache.
*/ */
public class TestProtocolAllowedBehavior extends AbstractProtocolTest { @RunWith(MockitoJUnitRunner.class)
public class TestProtocolAllowedBehavior {
static final int MAX_BYTES = 1024;
static final int MAX_ENTRIES = 100;
static final int ENTITY_LENGTH = 128;
HttpHost host;
HttpRoute route;
HttpClientContext context;
@Mock
ExecChain mockExecChain;
@Mock
ExecRuntime mockExecRuntime;
@Mock
HttpCache mockCache;
ClassicHttpRequest request;
ClassicHttpResponse originResponse;
CacheConfig config;
CachingExec impl;
HttpCache cache;
@Before
public void setUp() throws Exception {
host = new HttpHost("foo.example.com", 80);
route = new HttpRoute(host);
request = new BasicClassicHttpRequest("GET", "/foo");
context = HttpClientContext.create();
originResponse = HttpTestUtils.make200Response();
config = CacheConfig.custom()
.setMaxCacheEntries(MAX_ENTRIES)
.setMaxObjectSize(MAX_BYTES)
.setSharedCache(false)
.build();
cache = new BasicHttpCache(config);
impl = new CachingExec(cache, null, config);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
}
public ClassicHttpResponse execute(final ClassicHttpRequest request) throws IOException, HttpException {
return impl.execute(
ClassicRequestBuilder.copy(request).build(),
new ExecChain.Scope("test", route, request, mockExecRuntime, context),
mockExecChain);
}
@Test @Test
public void testNonSharedCacheReturnsStaleResponseWhenRevalidationFailsForProxyRevalidate() public void testNonSharedCacheReturnsStaleResponseWhenRevalidationFailsForProxyRevalidate() throws Exception {
throws Exception {
final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET","/"); final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET","/");
final Date now = new Date(); final Date now = new Date();
final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L); final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
@ -53,37 +117,33 @@ public class TestProtocolAllowedBehavior extends AbstractProtocolTest {
originResponse.setHeader("Cache-Control","max-age=5,proxy-revalidate"); originResponse.setHeader("Cache-Control","max-age=5,proxy-revalidate");
originResponse.setHeader("Etag","\"etag\""); originResponse.setHeader("Etag","\"etag\"");
backendExpectsAnyRequest().andReturn(originResponse);
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET","/"); final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET","/");
backendExpectsAnyRequest().andThrow(new SocketTimeoutException()); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
replayMocks();
behaveAsNonSharedCache();
execute(req1); execute(req1);
final HttpResponse result = execute(req2);
verifyMocks();
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenThrow(new SocketTimeoutException());
final HttpResponse result = execute(req2);
Assert.assertEquals(HttpStatus.SC_OK, result.getCode()); Assert.assertEquals(HttpStatus.SC_OK, result.getCode());
Mockito.verifyNoInteractions(mockCache);
} }
@Test @Test
public void testNonSharedCacheMayCacheResponsesWithCacheControlPrivate() public void testNonSharedCacheMayCacheResponsesWithCacheControlPrivate() throws Exception {
throws Exception {
final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET","/"); final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET","/");
originResponse.setHeader("Cache-Control","private,max-age=3600"); originResponse.setHeader("Cache-Control","private,max-age=3600");
backendExpectsAnyRequest().andReturn(originResponse);
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET","/"); final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET","/");
replayMocks(); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
behaveAsNonSharedCache();
execute(req1); execute(req1);
final HttpResponse result = execute(req2); final HttpResponse result = execute(req2);
verifyMocks();
Assert.assertEquals(HttpStatus.SC_OK, result.getCode()); Assert.assertEquals(HttpStatus.SC_OK, result.getCode());
Mockito.verifyNoInteractions(mockCache);
} }
} }

View File

@ -42,18 +42,18 @@ import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost; 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.HttpResponse;
import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.io.entity.ByteArrayEntity; import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
/** /**
* We are a conditionally-compliant HTTP/1.1 client with a cache. However, a lot * We are a conditionally-compliant HTTP/1.1 client with a cache. However, a lot
@ -70,22 +70,27 @@ import org.junit.Test;
* document the places where we differ from the HTTP RFC. * document the places where we differ from the HTTP RFC.
*/ */
@SuppressWarnings("boxing") // test code @SuppressWarnings("boxing") // test code
@RunWith(MockitoJUnitRunner.class)
public class TestProtocolDeviations { public class TestProtocolDeviations {
private static final int MAX_BYTES = 1024; private static final int MAX_BYTES = 1024;
private static final int MAX_ENTRIES = 100; private static final int MAX_ENTRIES = 100;
private HttpHost host; HttpHost host;
private HttpRoute route; HttpRoute route;
private HttpEntity mockEntity; @Mock
private ExecRuntime mockEndpoint; HttpEntity mockEntity;
private ExecChain mockExecChain; @Mock
private HttpCache mockCache; ExecRuntime mockEndpoint;
private ClassicHttpRequest request; @Mock
private HttpCacheContext context; ExecChain mockExecChain;
private ClassicHttpResponse originResponse; @Mock
HttpCache mockCache;
ClassicHttpRequest request;
HttpCacheContext context;
ClassicHttpResponse originResponse;
private ExecChainHandler impl; ExecChainHandler impl;
@Before @Before
public void setUp() { public void setUp() {
@ -105,11 +110,6 @@ public class TestProtocolDeviations {
.build(); .build();
final HttpCache cache = new BasicHttpCache(config); final HttpCache cache = new BasicHttpCache(config);
mockEndpoint = EasyMock.createNiceMock(ExecRuntime.class);
mockExecChain = EasyMock.createNiceMock(ExecChain.class);
mockEntity = EasyMock.createNiceMock(HttpEntity.class);
mockCache = EasyMock.createNiceMock(HttpCache.class);
impl = createCachingExecChain(cache, config); impl = createCachingExecChain(cache, config);
} }
@ -131,122 +131,12 @@ public class TestProtocolDeviations {
return out; return out;
} }
private void replayMocks() {
EasyMock.replay(mockExecChain);
EasyMock.replay(mockCache);
EasyMock.replay(mockEntity);
}
private void verifyMocks() {
EasyMock.verify(mockExecChain);
EasyMock.verify(mockCache);
EasyMock.verify(mockEntity);
}
private HttpEntity makeBody(final int nbytes) { private HttpEntity makeBody(final int nbytes) {
final byte[] bytes = new byte[nbytes]; final byte[] bytes = new byte[nbytes];
new Random().nextBytes(bytes); new Random().nextBytes(bytes);
return new ByteArrayEntity(bytes, null); return new ByteArrayEntity(bytes, null);
} }
public static HttpRequest eqRequest(final HttpRequest in) {
org.easymock.EasyMock.reportMatcher(new RequestEquivalent(in));
return null;
}
/*
* "For compatibility with HTTP/1.0 applications, HTTP/1.1 requests
* containing a message-body MUST include a valid Content-Length header
* field unless the server is known to be HTTP/1.1 compliant. If a request
* contains a message-body and a Content-Length is not given, the server
* SHOULD respond with 400 (bad request) if it cannot determine the length
* of the message, or with 411 (length required) if it wishes to insist on
* receiving a valid Content-Length."
*
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4
*
* 8/23/2010 JRC - This test has been moved to Ignore. The caching client
* was set to return status code 411 on a missing content-length header when
* a request had a body. It seems that somewhere deeper in the client stack
* this header is added automatically for us - so the caching client shouldn't
* specifically be worried about this requirement.
*/
@Ignore
public void testHTTP1_1RequestsWithBodiesOfKnownLengthMustHaveContentLength() throws Exception {
final ClassicHttpRequest post = new BasicClassicHttpRequest("POST", "/");
post.setEntity(mockEntity);
replayMocks();
final HttpResponse response = execute(post);
verifyMocks();
Assert.assertEquals(HttpStatus.SC_LENGTH_REQUIRED, response.getCode());
}
/*
* Discussion: if an incoming request has a body, but the HttpEntity
* attached has an unknown length (meaning entity.getContentLength() is
* negative), we have two choices if we want to be conditionally compliant.
* (1) we can slurp the whole body into a bytearray and compute its length
* before sending; or (2) we can push responsibility for (1) back onto the
* client by just generating a 411 response
*
* There is a third option, which is that we delegate responsibility for (1)
* onto the backend HttpClient, but because that is an injected dependency,
* we can't rely on it necessarily being conditionally compliant with
* HTTP/1.1. Currently, option (2) seems like the safest bet, as this
* exposes to the client application that the slurping required for (1)
* needs to happen in order to compute the content length.
*
* In any event, this test just captures the behavior required.
*
* 8/23/2010 JRC - This test has been moved to Ignore. The caching client
* was set to return status code 411 on a missing content-length header when
* a request had a body. It seems that somewhere deeper in the client stack
* this header is added automatically for us - so the caching client shouldn't
* specifically be worried about this requirement.
*/
@Ignore
public void testHTTP1_1RequestsWithUnknownBodyLengthAreRejectedOrHaveContentLengthAdded()
throws Exception {
final ClassicHttpRequest post = new BasicClassicHttpRequest("POST", "/");
final byte[] bytes = new byte[128];
new Random().nextBytes(bytes);
final HttpEntity mockBody = EasyMock.createMockBuilder(ByteArrayEntity.class).withConstructor(
new Object[] { bytes }).addMockedMethods("getContentLength").createNiceMock();
org.easymock.EasyMock.expect(mockBody.getContentLength()).andReturn(-1L).anyTimes();
post.setEntity(mockBody);
final Capture<ClassicHttpRequest> reqCap = EasyMock.newCapture();
EasyMock.expect(
mockExecChain.proceed(
EasyMock.capture(reqCap),
EasyMock.isA(ExecChain.Scope.class))).andReturn(
originResponse).times(0, 1);
replayMocks();
EasyMock.replay(mockBody);
final HttpResponse result = execute(post);
verifyMocks();
EasyMock.verify(mockBody);
if (reqCap.hasCaptured()) {
// backend request was made
final HttpRequest forwarded = reqCap.getValue();
Assert.assertNotNull(forwarded.getFirstHeader("Content-Length"));
} else {
final int status = result.getCode();
Assert.assertTrue(HttpStatus.SC_LENGTH_REQUIRED == status
|| HttpStatus.SC_BAD_REQUEST == status);
}
}
/* /*
* "10.2.7 206 Partial Content ... The request MUST have included a Range * "10.2.7 206 Partial Content ... The request MUST have included a Range
* header field (section 14.35) indicating the desired range, and MAY have * header field (section 14.35) indicating the desired range, and MAY have
@ -266,12 +156,8 @@ public class TestProtocolDeviations {
originResponse.setHeader("Content-Range", "bytes 0-499/1234"); originResponse.setHeader("Content-Range", "bytes 0-499/1234");
originResponse.setEntity(makeBody(500)); originResponse.setEntity(makeBody(500));
EasyMock.expect( Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
mockExecChain.proceed(
EasyMock.isA(ClassicHttpRequest.class),
EasyMock.isA(ExecChain.Scope.class))).andReturn(originResponse);
replayMocks();
try { try {
final HttpResponse result = execute(request); final HttpResponse result = execute(request);
Assert.assertTrue(HttpStatus.SC_PARTIAL_CONTENT != result.getCode()); Assert.assertTrue(HttpStatus.SC_PARTIAL_CONTENT != result.getCode());
@ -292,13 +178,9 @@ public class TestProtocolDeviations {
originResponse = new BasicClassicHttpResponse(401, "Unauthorized"); originResponse = new BasicClassicHttpResponse(401, "Unauthorized");
EasyMock.expect( Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
mockExecChain.proceed(
EasyMock.isA(ClassicHttpRequest.class),
EasyMock.isA(ExecChain.Scope.class))).andReturn(originResponse);
replayMocks();
final HttpResponse result = execute(request); final HttpResponse result = execute(request);
verifyMocks();
Assert.assertSame(originResponse, result); Assert.assertSame(originResponse, result);
} }
@ -312,13 +194,9 @@ public class TestProtocolDeviations {
public void testPassesOnOrigin405WithoutAllowHeader() throws Exception { public void testPassesOnOrigin405WithoutAllowHeader() throws Exception {
originResponse = new BasicClassicHttpResponse(405, "Method Not Allowed"); originResponse = new BasicClassicHttpResponse(405, "Method Not Allowed");
EasyMock.expect( Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
mockExecChain.proceed(
EasyMock.isA(ClassicHttpRequest.class),
EasyMock.isA(ExecChain.Scope.class))).andReturn(originResponse);
replayMocks();
final HttpResponse result = execute(request); final HttpResponse result = execute(request);
verifyMocks();
Assert.assertSame(originResponse, result); Assert.assertSame(originResponse, result);
} }
@ -333,13 +211,9 @@ public class TestProtocolDeviations {
public void testPassesOnOrigin407WithoutAProxyAuthenticateHeader() throws Exception { public void testPassesOnOrigin407WithoutAProxyAuthenticateHeader() throws Exception {
originResponse = new BasicClassicHttpResponse(407, "Proxy Authentication Required"); originResponse = new BasicClassicHttpResponse(407, "Proxy Authentication Required");
EasyMock.expect( Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
mockExecChain.proceed(
EasyMock.isA(ClassicHttpRequest.class),
EasyMock.isA(ExecChain.Scope.class))).andReturn(originResponse);
replayMocks();
final HttpResponse result = execute(request); final HttpResponse result = execute(request);
verifyMocks();
Assert.assertSame(originResponse, result); Assert.assertSame(originResponse, result);
} }

View File

@ -39,44 +39,101 @@ import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.auth.StandardAuthScheme; import org.apache.hc.client5.http.auth.StandardAuthScheme;
import org.apache.hc.client5.http.classic.ExecChain; import org.apache.hc.client5.http.classic.ExecChain;
import org.apache.hc.client5.http.classic.ExecRuntime;
import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.utils.DateUtils; import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HeaderElement; import org.apache.hc.core5.http.HeaderElement;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.HttpVersion;
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.http.message.MessageSupport;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
/* /*
* This test class captures functionality required to achieve unconditional * This test class captures functionality required to achieve unconditional
* compliance with the HTTP/1.1 spec, i.e. all the SHOULD, SHOULD NOT, * compliance with the HTTP/1.1 spec, i.e. all the SHOULD, SHOULD NOT,
* RECOMMENDED, and NOT RECOMMENDED behaviors. * RECOMMENDED, and NOT RECOMMENDED behaviors.
*/ */
public class TestProtocolRecommendations extends AbstractProtocolTest { @RunWith(MockitoJUnitRunner.class)
public class TestProtocolRecommendations {
private Date now; static final int MAX_BYTES = 1024;
private Date tenSecondsAgo; static final int MAX_ENTRIES = 100;
private Date twoMinutesAgo; static final int ENTITY_LENGTH = 128;
HttpHost host;
HttpRoute route;
HttpEntity body;
HttpClientContext context;
@Mock
ExecChain mockExecChain;
@Mock
ExecRuntime mockExecRuntime;
@Mock
HttpCache mockCache;
ClassicHttpRequest request;
ClassicHttpResponse originResponse;
CacheConfig config;
CachingExec impl;
HttpCache cache;
Date now;
Date tenSecondsAgo;
Date twoMinutesAgo;
@Override
@Before @Before
public void setUp() { public void setUp() throws Exception {
super.setUp(); host = new HttpHost("foo.example.com", 80);
route = new HttpRoute(host);
body = HttpTestUtils.makeBody(ENTITY_LENGTH);
request = new BasicClassicHttpRequest("GET", "/foo");
context = HttpClientContext.create();
originResponse = HttpTestUtils.make200Response();
config = CacheConfig.custom()
.setMaxCacheEntries(MAX_ENTRIES)
.setMaxObjectSize(MAX_BYTES)
.build();
cache = new BasicHttpCache(config);
impl = new CachingExec(cache, null, config);
now = new Date(); now = new Date();
tenSecondsAgo = new Date(now.getTime() - 10 * 1000L); tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
twoMinutesAgo = new Date(now.getTime() - 2 * 60 * 1000L); twoMinutesAgo = new Date(now.getTime() - 2 * 60 * 1000L);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
}
public ClassicHttpResponse execute(final ClassicHttpRequest request) throws IOException, HttpException {
return impl.execute(
ClassicRequestBuilder.copy(request).build(),
new ExecChain.Scope("test", route, request, mockExecRuntime, context),
mockExecChain);
} }
/* "identity: The default (identity) encoding; the use of no /* "identity: The default (identity) encoding; the use of no
@ -89,10 +146,10 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
@Test @Test
public void testIdentityCodingIsNotUsedInContentEncodingHeader() throws Exception { public void testIdentityCodingIsNotUsedInContentEncodingHeader() throws Exception {
originResponse.setHeader("Content-Encoding", "identity"); originResponse.setHeader("Content-Encoding", "identity");
backendExpectsAnyRequest().andReturn(originResponse); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
replayMocks();
final ClassicHttpResponse result = execute(request); final ClassicHttpResponse result = execute(request);
verifyMocks();
boolean foundIdentity = false; boolean foundIdentity = false;
final Iterator<HeaderElement> it = MessageSupport.iterate(result, HttpHeaders.CONTENT_ENCODING); final Iterator<HeaderElement> it = MessageSupport.iterate(result, HttpHeaders.CONTENT_ENCODING);
while (it.hasNext()) { while (it.hasNext()) {
@ -120,15 +177,14 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader(validatorHeader, validator); resp1.setHeader(validatorHeader, validator);
resp1.setHeader(headerName, headerValue); resp1.setHeader(headerName, headerValue);
backendExpectsAnyRequestAndReturn(resp1); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader(conditionalHeader, validator); req2.setHeader(conditionalHeader, validator);
replayMocks();
execute(req1); execute(req1);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
if (HttpStatus.SC_NOT_MODIFIED == result.getCode()) { if (HttpStatus.SC_NOT_MODIFIED == result.getCode()) {
assertNull(result.getFirstHeader(headerName)); assertNull(result.getFirstHeader(headerName));
@ -217,7 +273,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader(validatorHeader, validator); resp1.setHeader(validatorHeader, validator);
resp1.setHeader("Content-Range", "bytes 0-127/256"); resp1.setHeader("Content-Range", "bytes 0-127/256");
backendExpectsAnyRequestAndReturn(resp1); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("If-Range", validator); req2.setHeader("If-Range", validator);
@ -230,19 +286,15 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
// cache module does not currently deal with byte ranges, but we want // cache module does not currently deal with byte ranges, but we want
// this test to work even if it does some day // this test to work even if it does some day
final Capture<ClassicHttpRequest> cap = EasyMock.newCapture();
EasyMock.expect(
mockExecChain.proceed(
EasyMock.capture(cap),
EasyMock.isA(ExecChain.Scope.class))).andReturn(resp2).times(0,1);
replayMocks();
execute(req1); execute(req1);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
if (!cap.hasCaptured() final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
&& HttpStatus.SC_NOT_MODIFIED == result.getCode()) { Mockito.verify(mockExecChain, Mockito.atMost(2)).proceed(reqCapture.capture(), Mockito.any());
final List<ClassicHttpRequest> allRequests = reqCapture.getAllValues();
if (allRequests.isEmpty() && HttpStatus.SC_NOT_MODIFIED == result.getCode()) {
// cache generated a 304 // cache generated a 304
assertNull(result.getFirstHeader("Content-Range")); assertNull(result.getFirstHeader("Content-Range"));
} }
@ -294,11 +346,9 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp.setHeader("Etag", "\"etag\""); resp.setHeader("Etag", "\"etag\"");
resp.setHeader(entityHeader, entityHeaderValue); resp.setHeader(entityHeader, entityHeaderValue);
backendExpectsAnyRequestAndReturn(resp); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp);
replayMocks();
final ClassicHttpResponse result = execute(req); final ClassicHttpResponse result = execute(req);
verifyMocks();
assertNull(result.getFirstHeader(entityHeader)); assertNull(result.getFirstHeader(entityHeader));
} }
@ -350,11 +400,9 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp.setHeader("ETag", "\"etag\""); resp.setHeader("ETag", "\"etag\"");
resp.setHeader("Content-Range", "bytes 0-127/256"); resp.setHeader("Content-Range", "bytes 0-127/256");
backendExpectsAnyRequestAndReturn(resp); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp);
replayMocks();
final ClassicHttpResponse result = execute(req); final ClassicHttpResponse result = execute(req);
verifyMocks();
assertNull(result.getFirstHeader("Content-Range")); assertNull(result.getFirstHeader("Content-Range"));
} }
@ -377,23 +425,22 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Cache-Control","public,max-age=5"); resp1.setHeader("Cache-Control","public,max-age=5");
resp1.setHeader("Etag","\"etag\""); resp1.setHeader("Etag","\"etag\"");
backendExpectsAnyRequestAndReturn(resp1); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
return req1; return req1;
} }
private void testDoesNotReturnStaleResponseOnError(final ClassicHttpRequest req2) throws Exception { private void testDoesNotReturnStaleResponseOnError(final ClassicHttpRequest req2) throws Exception {
final ClassicHttpRequest req1 = requestToPopulateStaleCacheEntry(); final ClassicHttpRequest req1 = requestToPopulateStaleCacheEntry();
backendExpectsAnyRequest().andThrow(new IOException());
replayMocks();
execute(req1); execute(req1);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenThrow(new IOException());
ClassicHttpResponse result = null; ClassicHttpResponse result = null;
try { try {
result = execute(req2); result = execute(req2);
} catch (final IOException acceptable) { } catch (final IOException acceptable) {
} }
verifyMocks();
if (result != null) { if (result != null) {
assertNotEquals(HttpStatus.SC_OK, result.getCode()); assertNotEquals(HttpStatus.SC_OK, result.getCode());
@ -451,15 +498,14 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
req2.setHeader("Cache-Control","max-stale=20"); req2.setHeader("Cache-Control","max-stale=20");
backendExpectsAnyRequest().andThrow(new IOException()).times(0,1);
replayMocks();
execute(req1); execute(req1);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
assertEquals(HttpStatus.SC_OK, result.getCode()); assertEquals(HttpStatus.SC_OK, result.getCode());
assertNotNull(result.getFirstHeader("Warning")); assertNotNull(result.getFirstHeader("Warning"));
Mockito.verify(mockExecChain, Mockito.atMost(1)).proceed(Mockito.any(), Mockito.any());
} }
/* /*
@ -503,16 +549,17 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("ETag","\"etag\""); resp1.setHeader("ETag","\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo)); resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
backendExpectsAnyRequest().andThrow(new IOException()).anyTimes(); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
replayMocks();
execute(req1); execute(req1);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenThrow(new IOException());
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
assertEquals(HttpStatus.SC_OK, result.getCode()); assertEquals(HttpStatus.SC_OK, result.getCode());
boolean warning111Found = false; boolean warning111Found = false;
@ -545,11 +592,9 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
originResponse.setHeader("Cache-Control","public, max-age=5"); originResponse.setHeader("Cache-Control","public, max-age=5");
originResponse.setHeader("ETag","\"etag\""); originResponse.setHeader("ETag","\"etag\"");
backendExpectsAnyRequest().andReturn(originResponse); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
replayMocks();
final ClassicHttpResponse result = execute(request); final ClassicHttpResponse result = execute(request);
verifyMocks();
assertNull(result.getFirstHeader("Warning")); assertNull(result.getFirstHeader("Warning"));
} }
@ -563,11 +608,9 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
final String warning = "110 fred \"Response is stale\""; final String warning = "110 fred \"Response is stale\"";
originResponse.addHeader("Warning",warning); originResponse.addHeader("Warning",warning);
backendExpectsAnyRequest().andReturn(originResponse); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
replayMocks();
final ClassicHttpResponse result = execute(request); final ClassicHttpResponse result = execute(request);
verifyMocks();
assertEquals(warning, result.getFirstHeader("Warning").getValue()); assertEquals(warning, result.getFirstHeader("Warning").getValue());
} }
@ -581,27 +624,23 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
private void testDoesNotModifyHeaderOnResponses(final String headerName) throws Exception { private void testDoesNotModifyHeaderOnResponses(final String headerName) throws Exception {
final String headerValue = HttpTestUtils final String headerValue = HttpTestUtils
.getCanonicalHeaderValue(originResponse, headerName); .getCanonicalHeaderValue(originResponse, headerName);
backendExpectsAnyRequest().andReturn(originResponse); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
replayMocks();
final ClassicHttpResponse result = execute(request); final ClassicHttpResponse result = execute(request);
verifyMocks();
assertEquals(headerValue, assertEquals(headerValue, result.getFirstHeader(headerName).getValue());
result.getFirstHeader(headerName).getValue());
} }
private void testDoesNotModifyHeaderOnRequests(final String headerName) throws Exception { private void testDoesNotModifyHeaderOnRequests(final String headerName) throws Exception {
final String headerValue = HttpTestUtils.getCanonicalHeaderValue(request, headerName); final String headerValue = HttpTestUtils.getCanonicalHeaderValue(request, headerName);
final Capture<ClassicHttpRequest> cap = EasyMock.newCapture(); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
EasyMock.expect(
mockExecChain.proceed(
EasyMock.capture(cap),
EasyMock.isA(ExecChain.Scope.class))).andReturn(originResponse);
replayMocks();
execute(request); execute(request);
verifyMocks();
assertEquals(headerValue, final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
HttpTestUtils.getCanonicalHeaderValue(cap.getValue(), Mockito.verify(mockExecChain).proceed(reqCapture.capture(), Mockito.any());
headerName));
assertEquals(headerValue, HttpTestUtils.getCanonicalHeaderValue(reqCapture.getValue(), headerName));
} }
@Test @Test
@ -834,25 +873,22 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Last-Modified", lmDate); resp1.setHeader("Last-Modified", lmDate);
resp1.setHeader("Cache-Control","max-age=5"); resp1.setHeader("Cache-Control","max-age=5");
backendExpectsAnyRequestAndReturn(resp1);
final Capture<ClassicHttpRequest> cap = EasyMock.newCapture();
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
EasyMock.expect( Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
mockExecChain.proceed(
EasyMock.capture(cap),
EasyMock.isA(ExecChain.Scope.class))).andReturn(resp2);
replayMocks();
execute(req1); execute(req1);
execute(req2);
verifyMocks();
final ClassicHttpRequest captured = cap.getValue(); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
final Header ifModifiedSince =
captured.getFirstHeader("If-Modified-Since"); execute(req2);
final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
Mockito.verify(mockExecChain, Mockito.times(2)).proceed(reqCapture.capture(), Mockito.any());
final ClassicHttpRequest captured = reqCapture.getValue();
final Header ifModifiedSince = captured.getFirstHeader("If-Modified-Since");
assertEquals(lmDate, ifModifiedSince.getValue()); assertEquals(lmDate, ifModifiedSince.getValue());
} }
@ -877,28 +913,24 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Cache-Control","max-age=5"); resp1.setHeader("Cache-Control","max-age=5");
resp1.setHeader("ETag", etag); resp1.setHeader("ETag", etag);
backendExpectsAnyRequestAndReturn(resp1);
final Capture<ClassicHttpRequest> cap = EasyMock.newCapture();
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
EasyMock.expect( Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
mockExecChain.proceed(
EasyMock.capture(cap),
EasyMock.isA(ExecChain.Scope.class))).andReturn(resp2);
replayMocks();
execute(req1); execute(req1);
execute(req2);
verifyMocks();
final ClassicHttpRequest captured = cap.getValue(); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
final Header ifModifiedSince =
captured.getFirstHeader("If-Modified-Since"); execute(req2);
final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
Mockito.verify(mockExecChain, Mockito.times(2)).proceed(reqCapture.capture(), Mockito.any());
final ClassicHttpRequest captured = reqCapture.getValue();
final Header ifModifiedSince = captured.getFirstHeader("If-Modified-Since");
assertEquals(lmDate, ifModifiedSince.getValue()); assertEquals(lmDate, ifModifiedSince.getValue());
final Header ifNoneMatch = final Header ifNoneMatch = captured.getFirstHeader("If-None-Match");
captured.getFirstHeader("If-None-Match");
assertEquals(etag, ifNoneMatch.getValue()); assertEquals(etag, ifNoneMatch.getValue());
} }
@ -922,7 +954,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Expires",DateUtils.formatDate(oneSecondAgo)); resp1.setHeader("Expires",DateUtils.formatDate(oneSecondAgo));
resp1.setHeader("Cache-Control", "public"); resp1.setHeader("Cache-Control", "public");
backendExpectsAnyRequestAndReturn(resp1); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
final ClassicHttpRequest revalidate = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest revalidate = new BasicClassicHttpRequest("GET", "/");
@ -933,18 +965,12 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp2.setHeader("Expires", DateUtils.formatDate(oneSecondFromNow)); resp2.setHeader("Expires", DateUtils.formatDate(oneSecondFromNow));
resp2.setHeader("ETag","\"etag\""); resp2.setHeader("ETag","\"etag\"");
EasyMock.expect( Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(revalidate), Mockito.any())).thenReturn(resp2);
mockExecChain.proceed(
eqRequest(revalidate),
EasyMock.isA(ExecChain.Scope.class))).andReturn(resp2);
replayMocks();
execute(req1); execute(req1);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
assertEquals(HttpStatus.SC_OK, assertEquals(HttpStatus.SC_OK, result.getCode());
result.getCode());
} }
/* "When a client tries to revalidate a cache entry, and the response /* "When a client tries to revalidate a cache entry, and the response
@ -969,32 +995,23 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo)); resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control","max-age=5"); resp1.setHeader("Cache-Control","max-age=5");
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
final ClassicHttpResponse resp2 = HttpTestUtils.make304Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make304Response();
resp2.setHeader("ETag","\"etag\""); resp2.setHeader("ETag","\"etag\"");
resp2.setHeader("Date", DateUtils.formatDate(elevenSecondsAgo)); resp2.setHeader("Date", DateUtils.formatDate(elevenSecondsAgo));
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
final Capture<ClassicHttpRequest> cap = EasyMock.newCapture();
final ClassicHttpResponse resp3 = HttpTestUtils.make200Response();
resp3.setHeader("ETag","\"etag2\"");
resp3.setHeader("Date", DateUtils.formatDate(now));
resp3.setHeader("Cache-Control","max-age=5");
EasyMock.expect(
mockExecChain.proceed(
EasyMock.capture(cap),
EasyMock.isA(ExecChain.Scope.class))).andReturn(resp3);
replayMocks();
execute(req1); execute(req1);
execute(req2);
verifyMocks();
final ClassicHttpRequest captured = cap.getValue(); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
execute(req2);
final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
Mockito.verify(mockExecChain, Mockito.times(3)).proceed(reqCapture.capture(), Mockito.any());
final ClassicHttpRequest captured = reqCapture.getValue();
boolean hasMaxAge0 = false; boolean hasMaxAge0 = false;
boolean hasNoCache = false; boolean hasNoCache = false;
final Iterator<HeaderElement> it = MessageSupport.iterate(captured, HttpHeaders.CACHE_CONTROL); final Iterator<HeaderElement> it = MessageSupport.iterate(captured, HttpHeaders.CACHE_CONTROL);
@ -1037,8 +1054,6 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Vary","User-Agent"); resp1.setHeader("Vary","User-Agent");
resp1.setHeader("Etag","\"etag1\""); resp1.setHeader("Etag","\"etag1\"");
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET","/"); final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET","/");
req2.setHeader("User-Agent","agent2"); req2.setHeader("User-Agent","agent2");
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
@ -1046,25 +1061,26 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp2.setHeader("Vary","User-Agent"); resp2.setHeader("Vary","User-Agent");
resp2.setHeader("Etag","\"etag2\""); resp2.setHeader("Etag","\"etag2\"");
backendExpectsAnyRequestAndReturn(resp2);
final Capture<ClassicHttpRequest> cap = EasyMock.newCapture();
final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET","/"); final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET","/");
req3.setHeader("User-Agent","agent3"); req3.setHeader("User-Agent","agent3");
final ClassicHttpResponse resp3 = HttpTestUtils.make200Response(); final ClassicHttpResponse resp3 = HttpTestUtils.make200Response();
EasyMock.expect( Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
mockExecChain.proceed(
EasyMock.capture(cap),
EasyMock.isA(ExecChain.Scope.class))).andReturn(resp3);
replayMocks();
execute(req1); execute(req1);
execute(req2);
execute(req3);
verifyMocks();
final ClassicHttpRequest captured = cap.getValue(); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
execute(req2);
Mockito.when(mockExecChain.proceed(Mockito.any(),Mockito.any())).thenReturn(resp3);
execute(req3);
final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
Mockito.verify(mockExecChain, Mockito.times(3)).proceed(reqCapture.capture(), Mockito.any());
final ClassicHttpRequest captured = reqCapture.getValue();
boolean foundEtag1 = false; boolean foundEtag1 = false;
boolean foundEtag2 = false; boolean foundEtag2 = false;
for(final Header h : captured.getHeaders("If-None-Match")) { for(final Header h : captured.getHeaders("If-None-Match")) {
@ -1099,9 +1115,6 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Cache-Control", "max-age=3600"); resp1.setHeader("Cache-Control", "max-age=3600");
resp1.setHeader("ETag", "\"etag1\""); resp1.setHeader("ETag", "\"etag1\"");
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
req2.setHeader("User-Agent", "agent2"); req2.setHeader("User-Agent", "agent2");
@ -1111,8 +1124,6 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp2.setHeader("Cache-Control", "max-age=3600"); resp2.setHeader("Cache-Control", "max-age=3600");
resp2.setHeader("ETag", "\"etag2\""); resp2.setHeader("ETag", "\"etag2\"");
backendExpectsAnyRequestAndReturn(resp2);
final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
req3.setHeader("User-Agent", "agent3"); req3.setHeader("User-Agent", "agent3");
@ -1120,17 +1131,21 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp3.setHeader("Date", DateUtils.formatDate(now)); resp3.setHeader("Date", DateUtils.formatDate(now));
resp3.setHeader("ETag", "\"etag1\""); resp3.setHeader("ETag", "\"etag1\"");
backendExpectsAnyRequestAndReturn(resp3);
final ClassicHttpRequest req4 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req4 = new BasicClassicHttpRequest("GET", "/");
req4.setHeader("User-Agent", "agent1"); req4.setHeader("User-Agent", "agent1");
replayMocks(); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
execute(req1); execute(req1);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
execute(req2); execute(req2);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
final ClassicHttpResponse result1 = execute(req3); final ClassicHttpResponse result1 = execute(req3);
final ClassicHttpResponse result2 = execute(req4); final ClassicHttpResponse result2 = execute(req4);
verifyMocks();
assertEquals(HttpStatus.SC_OK, result1.getCode()); assertEquals(HttpStatus.SC_OK, result1.getCode());
assertEquals("\"etag1\"", result1.getFirstHeader("ETag").getValue()); assertEquals("\"etag1\"", result1.getFirstHeader("ETag").getValue());
@ -1150,7 +1165,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Cache-Control", "max-age=3600"); resp1.setHeader("Cache-Control", "max-age=3600");
resp1.setHeader("ETag", "\"etag1\""); resp1.setHeader("ETag", "\"etag1\"");
backendExpectsAnyRequestAndReturn(resp1); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
req2.setHeader("User-Agent", "agent2"); req2.setHeader("User-Agent", "agent2");
@ -1159,16 +1174,14 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp2.setHeader("Date", DateUtils.formatDate(now)); resp2.setHeader("Date", DateUtils.formatDate(now));
resp2.setHeader("ETag", "\"etag1\""); resp2.setHeader("ETag", "\"etag1\"");
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
req3.setHeader("User-Agent", "agent2"); req3.setHeader("User-Agent", "agent2");
replayMocks();
execute(req1); execute(req1);
execute(req2); execute(req2);
execute(req3); execute(req3);
verifyMocks();
} }
/* "If any of the existing cache entries contains only partial content /* "If any of the existing cache entries contains only partial content
@ -1187,8 +1200,6 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Vary", "User-Agent"); resp1.setHeader("Vary", "User-Agent");
resp1.setHeader("ETag", "\"etag1\""); resp1.setHeader("ETag", "\"etag1\"");
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("User-Agent", "agent2"); req2.setHeader("User-Agent", "agent2");
req2.setHeader("Range", "bytes=0-49"); req2.setHeader("Range", "bytes=0-49");
@ -1201,8 +1212,6 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp2.setHeader("Cache-Control","max-age=3600"); resp2.setHeader("Cache-Control","max-age=3600");
resp2.setHeader("Date", DateUtils.formatDate(new Date())); resp2.setHeader("Date", DateUtils.formatDate(new Date()));
backendExpectsAnyRequestAndReturn(resp2);
final ClassicHttpRequest req3 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req3 = HttpTestUtils.makeDefaultRequest();
req3.setHeader("User-Agent", "agent3"); req3.setHeader("User-Agent", "agent3");
@ -1211,19 +1220,22 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Vary", "User-Agent"); resp1.setHeader("Vary", "User-Agent");
resp1.setHeader("ETag", "\"etag3\""); resp1.setHeader("ETag", "\"etag3\"");
final Capture<ClassicHttpRequest> cap = EasyMock.newCapture(); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
EasyMock.expect(
mockExecChain.proceed(
EasyMock.capture(cap),
EasyMock.isA(ExecChain.Scope.class))).andReturn(resp3);
replayMocks();
execute(req1); execute(req1);
execute(req2);
execute(req3);
verifyMocks();
final ClassicHttpRequest captured = cap.getValue(); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
execute(req2);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
execute(req3);
final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
Mockito.verify(mockExecChain, Mockito.times(3)).proceed(reqCapture.capture(), Mockito.any());
final ClassicHttpRequest captured = reqCapture.getValue();
final Iterator<HeaderElement> it = MessageSupport.iterate(captured, HttpHeaders.IF_NONE_MATCH); final Iterator<HeaderElement> it = MessageSupport.iterate(captured, HttpHeaders.IF_NONE_MATCH);
while (it.hasNext()) { while (it.hasNext()) {
final HeaderElement elt = it.next(); final HeaderElement elt = it.next();
@ -1248,7 +1260,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("ETag", "\"old-etag\""); resp1.setHeader("ETag", "\"old-etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo)); resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
backendExpectsAnyRequestAndReturn(resp1); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
final ClassicHttpRequest req2 = new HttpPost("http://foo.example.com/bar"); final ClassicHttpRequest req2 = new HttpPost("http://foo.example.com/bar");
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
@ -1256,18 +1268,16 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp2.setHeader("Date", DateUtils.formatDate(now)); resp2.setHeader("Date", DateUtils.formatDate(now));
resp2.setHeader("Content-Location", "http://foo.example.com/"); resp2.setHeader("Content-Location", "http://foo.example.com/");
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
final ClassicHttpRequest req3 = new HttpGet("http://foo.example.com"); final ClassicHttpRequest req3 = new HttpGet("http://foo.example.com");
final ClassicHttpResponse resp3 = HttpTestUtils.make200Response(); final ClassicHttpResponse resp3 = HttpTestUtils.make200Response();
backendExpectsAnyRequestAndReturn(resp3); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
replayMocks();
execute(req1); execute(req1);
execute(req2); execute(req2);
execute(req3); execute(req3);
verifyMocks();
} }
/* /*
@ -1286,11 +1296,9 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp2.setHeader("Content-Length","200"); resp2.setHeader("Content-Length","200");
resp2.setHeader("Date", DateUtils.formatDate(now)); resp2.setHeader("Date", DateUtils.formatDate(now));
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
replayMocks();
execute(req2); execute(req2);
verifyMocks();
} }
@Test @Test
@ -1303,11 +1311,9 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp2.setHeader("Date", DateUtils.formatDate(now)); resp2.setHeader("Date", DateUtils.formatDate(now));
resp2.setHeader("Via","1.0 someproxy"); resp2.setHeader("Via","1.0 someproxy");
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
replayMocks();
execute(req2); execute(req2);
verifyMocks();
} }
/* /*
@ -1323,25 +1329,23 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(); final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Cache-Control","max-age=3600"); resp1.setHeader("Cache-Control","max-age=3600");
backendExpectsAnyRequestAndReturn(resp1); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("FROB", "/"); final ClassicHttpRequest req2 = new BasicClassicHttpRequest("FROB", "/");
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
resp2.setHeader("Cache-Control","max-age=3600"); resp2.setHeader("Cache-Control","max-age=3600");
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
final ClassicHttpResponse resp3 = HttpTestUtils.make200Response(); final ClassicHttpResponse resp3 = HttpTestUtils.make200Response();
resp3.setHeader("ETag", "\"etag\""); resp3.setHeader("ETag", "\"etag\"");
backendExpectsAnyRequestAndReturn(resp3); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
replayMocks();
execute(req1); execute(req1);
execute(req2); execute(req2);
final ClassicHttpResponse result = execute(req3); final ClassicHttpResponse result = execute(req3);
verifyMocks();
assertTrue(HttpTestUtils.semanticallyTransparent(resp3, result)); assertTrue(HttpTestUtils.semanticallyTransparent(resp3, result));
} }
@ -1354,44 +1358,46 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Cache-Control","max-age=3600"); resp1.setHeader("Cache-Control","max-age=3600");
resp1.setHeader("Vary", "User-Agent"); resp1.setHeader("Vary", "User-Agent");
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
req2.setHeader("User-Agent", "agent2"); req2.setHeader("User-Agent", "agent2");
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
resp2.setHeader("Cache-Control","max-age=3600"); resp2.setHeader("Cache-Control","max-age=3600");
resp2.setHeader("Vary", "User-Agent"); resp2.setHeader("Vary", "User-Agent");
backendExpectsAnyRequestAndReturn(resp2);
final ClassicHttpRequest req3 = new BasicClassicHttpRequest("FROB", "/"); final ClassicHttpRequest req3 = new BasicClassicHttpRequest("FROB", "/");
req3.setHeader("User-Agent", "agent3"); req3.setHeader("User-Agent", "agent3");
final ClassicHttpResponse resp3 = HttpTestUtils.make200Response(); final ClassicHttpResponse resp3 = HttpTestUtils.make200Response();
resp3.setHeader("Cache-Control","max-age=3600"); resp3.setHeader("Cache-Control","max-age=3600");
backendExpectsAnyRequestAndReturn(resp3);
final ClassicHttpRequest req4 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req4 = new BasicClassicHttpRequest("GET", "/");
req4.setHeader("User-Agent", "agent1"); req4.setHeader("User-Agent", "agent1");
final ClassicHttpResponse resp4 = HttpTestUtils.make200Response(); final ClassicHttpResponse resp4 = HttpTestUtils.make200Response();
resp4.setHeader("ETag", "\"etag1\""); resp4.setHeader("ETag", "\"etag1\"");
backendExpectsAnyRequestAndReturn(resp4);
final ClassicHttpRequest req5 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req5 = new BasicClassicHttpRequest("GET", "/");
req5.setHeader("User-Agent", "agent2"); req5.setHeader("User-Agent", "agent2");
final ClassicHttpResponse resp5 = HttpTestUtils.make200Response(); final ClassicHttpResponse resp5 = HttpTestUtils.make200Response();
resp5.setHeader("ETag", "\"etag2\""); resp5.setHeader("ETag", "\"etag2\"");
backendExpectsAnyRequestAndReturn(resp5); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
replayMocks();
execute(req1); execute(req1);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
execute(req2); execute(req2);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
execute(req3); execute(req3);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp4);
final ClassicHttpResponse result4 = execute(req4); final ClassicHttpResponse result4 = execute(req4);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp5);
final ClassicHttpResponse result5 = execute(req5); final ClassicHttpResponse result5 = execute(req5);
verifyMocks();
assertTrue(HttpTestUtils.semanticallyTransparent(resp4, result4)); assertTrue(HttpTestUtils.semanticallyTransparent(resp4, result4));
assertTrue(HttpTestUtils.semanticallyTransparent(resp5, result5)); assertTrue(HttpTestUtils.semanticallyTransparent(resp5, result5));
@ -1412,7 +1418,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Cache-Control", "max-age=3600"); resp1.setHeader("Cache-Control", "max-age=3600");
resp1.setHeader("ETag", "\"etag1\""); resp1.setHeader("ETag", "\"etag1\"");
backendExpectsAnyRequestAndReturn(resp1); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control", "max-age=0"); req2.setHeader("Cache-Control", "max-age=0");
@ -1421,15 +1427,13 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp2.setHeader("Cache-Control", "max-age=3600"); resp2.setHeader("Cache-Control", "max-age=3600");
resp2.setHeader("ETag", "\"etag2\""); resp2.setHeader("ETag", "\"etag2\"");
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
final ClassicHttpRequest req3 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req3 = HttpTestUtils.makeDefaultRequest();
replayMocks();
execute(req1); execute(req1);
execute(req2); execute(req2);
final ClassicHttpResponse result = execute(req3); final ClassicHttpResponse result = execute(req3);
verifyMocks();
assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result)); assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result));
} }
@ -1453,19 +1457,17 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Expires", DateUtils.formatDate(now)); resp1.setHeader("Expires", DateUtils.formatDate(now));
resp1.removeHeaders("Cache-Control"); resp1.removeHeaders("Cache-Control");
backendExpectsAnyRequestAndReturn(resp1); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control", "max-stale=1000"); req2.setHeader("Cache-Control", "max-stale=1000");
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
resp2.setHeader("ETag", "\"etag2\""); resp2.setHeader("ETag", "\"etag2\"");
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
replayMocks();
execute(req1); execute(req1);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result)); assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result));
} }
@ -1478,19 +1480,17 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Expires", DateUtils.formatDate(tenSecondsAgo)); resp1.setHeader("Expires", DateUtils.formatDate(tenSecondsAgo));
resp1.removeHeaders("Cache-Control"); resp1.removeHeaders("Cache-Control");
backendExpectsAnyRequestAndReturn(resp1); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control", "max-stale=1000"); req2.setHeader("Cache-Control", "max-stale=1000");
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
resp2.setHeader("ETag", "\"etag2\""); resp2.setHeader("ETag", "\"etag2\"");
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
replayMocks();
execute(req1); execute(req1);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result)); assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result));
} }
@ -1507,18 +1507,12 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
req1.setHeader("Cache-Control", "min-fresh=10, no-cache"); req1.setHeader("Cache-Control", "min-fresh=10, no-cache");
req1.addHeader("Cache-Control", "max-stale=0, max-age=0"); req1.addHeader("Cache-Control", "max-stale=0, max-age=0");
final Capture<ClassicHttpRequest> cap = EasyMock.newCapture();
EasyMock.expect(
mockExecChain.proceed(
EasyMock.capture(cap),
EasyMock.isA(ExecChain.Scope.class))).andReturn(HttpTestUtils.make200Response());
replayMocks();
execute(req1); execute(req1);
verifyMocks();
final ClassicHttpRequest captured = cap.getValue(); final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
Mockito.verify(mockExecChain).proceed(reqCapture.capture(), Mockito.any());
final ClassicHttpRequest captured = reqCapture.getValue();
boolean foundNoCache = false; boolean foundNoCache = false;
boolean foundDisallowedDirective = false; boolean foundDisallowedDirective = false;
final List<String> disallowed = final List<String> disallowed =
@ -1551,12 +1545,9 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
final ClassicHttpRequest req = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req = HttpTestUtils.makeDefaultRequest();
req.setHeader("Cache-Control", "only-if-cached"); req.setHeader("Cache-Control", "only-if-cached");
replayMocks();
final ClassicHttpResponse result = execute(req); final ClassicHttpResponse result = execute(req);
verifyMocks();
assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT, assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT, result.getCode());
result.getCode());
} }
@Test @Test
@ -1565,15 +1556,13 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(); final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Cache-Control","max-age=3600"); resp1.setHeader("Cache-Control","max-age=3600");
backendExpectsAnyRequestAndReturn(resp1); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control", "only-if-cached"); req2.setHeader("Cache-Control", "only-if-cached");
replayMocks();
execute(req1); execute(req1);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
assertTrue(HttpTestUtils.semanticallyTransparent(resp1, result)); assertTrue(HttpTestUtils.semanticallyTransparent(resp1, result));
} }
@ -1585,18 +1574,15 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo)); resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control","max-age=5"); resp1.setHeader("Cache-Control","max-age=5");
backendExpectsAnyRequestAndReturn(resp1); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control", "only-if-cached"); req2.setHeader("Cache-Control", "only-if-cached");
replayMocks();
execute(req1); execute(req1);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT, assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT, result.getCode());
result.getCode());
} }
@Test @Test
@ -1607,15 +1593,13 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo)); resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control","max-age=5"); resp1.setHeader("Cache-Control","max-age=5");
backendExpectsAnyRequestAndReturn(resp1); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control", "max-stale=20, only-if-cached"); req2.setHeader("Cache-Control", "max-stale=20, only-if-cached");
replayMocks();
execute(req1); execute(req1);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
assertTrue(HttpTestUtils.semanticallyTransparent(resp1, result)); assertTrue(HttpTestUtils.semanticallyTransparent(resp1, result));
} }
@ -1628,18 +1612,15 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Cache-Control", "max-age=300"); resp1.setHeader("Cache-Control", "max-age=300");
resp1.setHeader("ETag","W/\"weak-sauce\""); resp1.setHeader("ETag","W/\"weak-sauce\"");
backendExpectsAnyRequestAndReturn(resp1); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("If-None-Match","W/\"weak-sauce\""); req2.setHeader("If-None-Match","W/\"weak-sauce\"");
replayMocks();
execute(req1); execute(req1);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode()); assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
} }
} }

View File

@ -31,37 +31,90 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Date; import java.util.Date;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ScheduledThreadPoolExecutor;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.classic.ExecChain;
import org.apache.hc.client5.http.classic.ExecRuntime;
import org.apache.hc.client5.http.impl.schedule.ImmediateSchedulingStrategy; import org.apache.hc.client5.http.impl.schedule.ImmediateSchedulingStrategy;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.utils.DateUtils; import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.io.entity.InputStreamEntity; import org.apache.hc.core5.http.io.entity.InputStreamEntity;
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
import org.easymock.EasyMock;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
/** /**
* A suite of acceptance tests for compliance with RFC5861, which * A suite of acceptance tests for compliance with RFC5861, which
* describes the stale-if-error and stale-while-revalidate * describes the stale-if-error and stale-while-revalidate
* Cache-Control extensions. * Cache-Control extensions.
*/ */
public class TestRFC5861Compliance extends AbstractProtocolTest { @RunWith(MockitoJUnitRunner.class)
public class TestRFC5861Compliance {
private ScheduledExecutorService executorService; static final int MAX_BYTES = 1024;
static final int MAX_ENTRIES = 100;
static final int ENTITY_LENGTH = 128;
HttpHost host;
HttpRoute route;
HttpEntity body;
HttpClientContext context;
@Mock
ExecChain mockExecChain;
@Mock
ExecRuntime mockExecRuntime;
@Mock
HttpCache mockCache;
ClassicHttpRequest request;
ClassicHttpResponse originResponse;
CacheConfig config;
CachingExec impl;
HttpCache cache;
ScheduledExecutorService executorService;
@Before @Before
public void setup() { public void setUp() throws Exception {
host = new HttpHost("foo.example.com", 80);
route = new HttpRoute(host);
body = HttpTestUtils.makeBody(ENTITY_LENGTH);
request = new BasicClassicHttpRequest("GET", "/foo");
context = HttpClientContext.create();
originResponse = HttpTestUtils.make200Response();
config = CacheConfig.custom()
.setMaxCacheEntries(MAX_ENTRIES)
.setMaxObjectSize(MAX_BYTES)
.build();
cache = new BasicHttpCache(config);
impl = new CachingExec(cache, null, config);
executorService = new ScheduledThreadPoolExecutor(1); executorService = new ScheduledThreadPoolExecutor(1);
EasyMock.expect(mockExecRuntime.fork(null)).andReturn(mockExecRuntime).anyTimes();
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
Mockito.when(mockExecRuntime.fork(null)).thenReturn(mockExecRuntime);
} }
@After @After
@ -69,16 +122,11 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
executorService.shutdownNow(); executorService.shutdownNow();
} }
@Override public ClassicHttpResponse execute(final ClassicHttpRequest request) throws IOException, HttpException {
protected void replayMocks() { return impl.execute(
super.replayMocks(); ClassicRequestBuilder.copy(request).build(),
EasyMock.replay(mockExecRuntime); new ExecChain.Scope("test", route, request, mockExecRuntime, context),
} mockExecChain);
@Override
protected void verifyMocks() {
super.verifyMocks();
EasyMock.verify(mockExecRuntime);
} }
/* /*
@ -104,17 +152,16 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo, final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5, stale-if-error=60"); "public, max-age=5, stale-if-error=60");
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
final ClassicHttpResponse resp2 = HttpTestUtils.make500Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
replayMocks();
execute(req1); execute(req1);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
HttpTestUtils.assert110WarningFound(result); HttpTestUtils.assert110WarningFound(result);
} }
@ -127,8 +174,6 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo, final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5, stale-if-error=60"); "public, max-age=5, stale-if-error=60");
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
final ClassicHttpResponse resp2 = HttpTestUtils.make500Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
final byte[] body101 = HttpTestUtils.getRandomBytes(101); final byte[] body101 = HttpTestUtils.getRandomBytes(101);
@ -137,12 +182,13 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
final HttpEntity entity = new InputStreamEntity(cis, 101, null); final HttpEntity entity = new InputStreamEntity(cis, 101, null);
resp2.setEntity(entity); resp2.setEntity(entity);
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
replayMocks();
execute(req1); execute(req1);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
execute(req2); execute(req2);
verifyMocks();
assertTrue(cis.wasClosed()); assertTrue(cis.wasClosed());
} }
@ -155,17 +201,16 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo, final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5, stale-if-error=60, must-revalidate"); "public, max-age=5, stale-if-error=60, must-revalidate");
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
final ClassicHttpResponse resp2 = HttpTestUtils.make500Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
replayMocks();
execute(req1); execute(req1);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
assertTrue(HttpStatus.SC_OK != result.getCode()); assertTrue(HttpStatus.SC_OK != result.getCode());
} }
@ -179,17 +224,16 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo, final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5, stale-if-error=60, proxy-revalidate"); "public, max-age=5, stale-if-error=60, proxy-revalidate");
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
final ClassicHttpResponse resp2 = HttpTestUtils.make500Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
replayMocks();
execute(req1); execute(req1);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
assertTrue(HttpStatus.SC_OK != result.getCode()); assertTrue(HttpStatus.SC_OK != result.getCode());
} }
@ -206,17 +250,16 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo, final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5, stale-if-error=60, proxy-revalidate"); "public, max-age=5, stale-if-error=60, proxy-revalidate");
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
final ClassicHttpResponse resp2 = HttpTestUtils.make500Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
replayMocks();
execute(req1); execute(req1);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
HttpTestUtils.assert110WarningFound(result); HttpTestUtils.assert110WarningFound(result);
} }
@ -229,18 +272,17 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo, final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5, stale-if-error=60"); "public, max-age=5, stale-if-error=60");
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control","min-fresh=2"); req2.setHeader("Cache-Control","min-fresh=2");
final ClassicHttpResponse resp2 = HttpTestUtils.make500Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
replayMocks();
execute(req1); execute(req1);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
assertTrue(HttpStatus.SC_OK != result.getCode()); assertTrue(HttpStatus.SC_OK != result.getCode());
} }
@ -253,18 +295,17 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo, final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5"); "public, max-age=5");
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control","public, stale-if-error=60"); req2.setHeader("Cache-Control","public, stale-if-error=60");
final ClassicHttpResponse resp2 = HttpTestUtils.make500Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
replayMocks();
execute(req1); execute(req1);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
HttpTestUtils.assert110WarningFound(result); HttpTestUtils.assert110WarningFound(result);
} }
@ -278,18 +319,17 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo)); resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control", "public, max-age=5"); resp1.setHeader("Cache-Control", "public, max-age=5");
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control", "public, stale-if-error=60"); req2.setHeader("Cache-Control", "public, stale-if-error=60");
final ClassicHttpResponse resp2 = HttpTestUtils.make500Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
replayMocks();
execute(req1); execute(req1);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
HttpTestUtils.assert110WarningFound(result); HttpTestUtils.assert110WarningFound(result);
} }
@ -303,17 +343,16 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo, final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5, stale-if-error=2"); "public, max-age=5, stale-if-error=2");
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
final ClassicHttpResponse resp2 = HttpTestUtils.make500Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
replayMocks();
execute(req1); execute(req1);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR,
result.getCode()); result.getCode());
@ -328,21 +367,19 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo, final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5"); "public, max-age=5");
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control","stale-if-error=2"); req2.setHeader("Cache-Control","stale-if-error=2");
final ClassicHttpResponse resp2 = HttpTestUtils.make500Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
replayMocks();
execute(req1); execute(req1);
final ClassicHttpResponse result = execute(req2);
verifyMocks();
assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
result.getCode());
final ClassicHttpResponse result = execute(req2);
assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, result.getCode());
} }
/* /*
@ -372,14 +409,12 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
resp1.setHeader("ETag","\"etag\""); resp1.setHeader("ETag","\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo)); resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
backendExpectsAnyRequestAndReturn(resp1).times(1,2);
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
replayMocks(); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
execute(req1); execute(req1);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
assertEquals(HttpStatus.SC_OK, result.getCode()); assertEquals(HttpStatus.SC_OK, result.getCode());
boolean warning110Found = false; boolean warning110Found = false;
@ -392,6 +427,9 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
} }
} }
assertTrue(warning110Found); assertTrue(warning110Found);
Mockito.verify(mockExecChain, Mockito.atLeastOnce()).proceed(Mockito.any(), Mockito.any());
Mockito.verify(mockExecChain, Mockito.atMost(2)).proceed(Mockito.any(), Mockito.any());
} }
@Test @Test
@ -409,14 +447,12 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15"); resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo)); resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
backendExpectsAnyRequestAndReturn(resp1).times(1, 2);
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
replayMocks(); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
execute(req1); execute(req1);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
assertEquals(HttpStatus.SC_OK, result.getCode()); assertEquals(HttpStatus.SC_OK, result.getCode());
boolean warning110Found = false; boolean warning110Found = false;
@ -429,6 +465,9 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
} }
} }
assertTrue(warning110Found); assertTrue(warning110Found);
Mockito.verify(mockExecChain, Mockito.atLeastOnce()).proceed(Mockito.any(), Mockito.any());
Mockito.verify(mockExecChain, Mockito.atMost(2)).proceed(Mockito.any(), Mockito.any());
} }
@Test @Test
@ -451,15 +490,13 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
resp1.setHeader("ETag","\"etag\""); resp1.setHeader("ETag","\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo)); resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
backendExpectsAnyRequestAndReturn(resp1).times(1,2);
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
req2.setHeader("If-None-Match","\"etag\""); req2.setHeader("If-None-Match","\"etag\"");
replayMocks(); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
execute(req1); execute(req1);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode()); assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
boolean warning110Found = false; boolean warning110Found = false;
@ -472,8 +509,10 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
} }
} }
assertTrue(warning110Found); assertTrue(warning110Found);
}
Mockito.verify(mockExecChain, Mockito.atLeastOnce()).proceed(Mockito.any(), Mockito.any());
Mockito.verify(mockExecChain, Mockito.atMost(2)).proceed(Mockito.any(), Mockito.any());
}
@Test @Test
public void testStaleWhileRevalidateYieldsToMustRevalidate() public void testStaleWhileRevalidateYieldsToMustRevalidate()
@ -495,20 +534,19 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
resp1.setHeader("ETag","\"etag\""); resp1.setHeader("ETag","\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo)); resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, must-revalidate"); resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, must-revalidate");
resp2.setHeader("ETag","\"etag\""); resp2.setHeader("ETag","\"etag\"");
resp2.setHeader("Date", DateUtils.formatDate(now)); resp2.setHeader("Date", DateUtils.formatDate(now));
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
replayMocks();
execute(req1); execute(req1);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
assertEquals(HttpStatus.SC_OK, result.getCode()); assertEquals(HttpStatus.SC_OK, result.getCode());
boolean warning110Found = false; boolean warning110Found = false;
@ -544,20 +582,19 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
resp1.setHeader("ETag","\"etag\""); resp1.setHeader("ETag","\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo)); resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, proxy-revalidate"); resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, proxy-revalidate");
resp2.setHeader("ETag","\"etag\""); resp2.setHeader("ETag","\"etag\"");
resp2.setHeader("Date", DateUtils.formatDate(now)); resp2.setHeader("Date", DateUtils.formatDate(now));
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
replayMocks();
execute(req1); execute(req1);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
assertEquals(HttpStatus.SC_OK, result.getCode()); assertEquals(HttpStatus.SC_OK, result.getCode());
boolean warning110Found = false; boolean warning110Found = false;
@ -593,8 +630,6 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
resp1.setHeader("ETag","\"etag\""); resp1.setHeader("ETag","\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo)); resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/"); final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
req2.setHeader("Cache-Control","min-fresh=2"); req2.setHeader("Cache-Control","min-fresh=2");
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response(); final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
@ -602,12 +637,13 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
resp2.setHeader("ETag","\"etag\""); resp2.setHeader("ETag","\"etag\"");
resp2.setHeader("Date", DateUtils.formatDate(now)); resp2.setHeader("Date", DateUtils.formatDate(now));
backendExpectsAnyRequestAndReturn(resp2); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
replayMocks();
execute(req1); execute(req1);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
final ClassicHttpResponse result = execute(req2); final ClassicHttpResponse result = execute(req2);
verifyMocks();
assertEquals(HttpStatus.SC_OK, result.getCode()); assertEquals(HttpStatus.SC_OK, result.getCode());
boolean warning110Found = false; boolean warning110Found = false;

View File

@ -70,7 +70,6 @@
<memcached.version>2.12.3</memcached.version> <memcached.version>2.12.3</memcached.version>
<slf4j.version>1.7.25</slf4j.version> <slf4j.version>1.7.25</slf4j.version>
<junit.version>4.13</junit.version> <junit.version>4.13</junit.version>
<easymock.version>3.6</easymock.version>
<mockito.version>3.10.0</mockito.version> <mockito.version>3.10.0</mockito.version>
<jna.version>5.2.0</jna.version> <jna.version>5.2.0</jna.version>
<hc.stylecheck.version>1</hc.stylecheck.version> <hc.stylecheck.version>1</hc.stylecheck.version>
@ -189,12 +188,6 @@
<version>${mockito.version}</version> <version>${mockito.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>${easymock.version}</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>