View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.hc.client5.http.impl.cache;
28  
29  import static org.mockito.Mockito.verify;
30  import static org.mockito.Mockito.verifyNoMoreInteractions;
31  import static org.mockito.Mockito.when;
32  
33  import java.io.IOException;
34  import java.net.URI;
35  import java.time.Instant;
36  import java.util.HashMap;
37  import java.util.Map;
38  
39  import org.apache.hc.client5.http.cache.HttpCacheEntry;
40  import org.apache.hc.client5.http.cache.HttpCacheStorage;
41  import org.apache.hc.client5.http.utils.DateUtils;
42  import org.apache.hc.core5.function.Resolver;
43  import org.apache.hc.core5.http.Header;
44  import org.apache.hc.core5.http.HttpHost;
45  import org.apache.hc.core5.http.HttpRequest;
46  import org.apache.hc.core5.http.HttpResponse;
47  import org.apache.hc.core5.http.HttpStatus;
48  import org.apache.hc.core5.http.message.BasicHeader;
49  import org.apache.hc.core5.http.message.BasicHttpRequest;
50  import org.apache.hc.core5.http.message.BasicHttpResponse;
51  import org.junit.jupiter.api.BeforeEach;
52  import org.junit.jupiter.api.Test;
53  import org.mockito.ArgumentMatchers;
54  import org.mockito.Mock;
55  import org.mockito.MockitoAnnotations;
56  import org.mockito.stubbing.Answer;
57  
58  public class TestDefaultCacheInvalidator {
59  
60      private DefaultCacheInvalidator impl;
61      private HttpHost host;
62      @Mock
63      private HttpCacheEntry mockEntry;
64      @Mock
65      private Resolver<URI, String> cacheKeyResolver;
66      @Mock
67      private HttpCacheStorage mockStorage;
68  
69      private Instant now;
70      private Instant tenSecondsAgo;
71  
72      @BeforeEach
73      public void setUp() {
74          MockitoAnnotations.openMocks(this);
75          now = Instant.now();
76          tenSecondsAgo = now.minusSeconds(10);
77  
78          when(cacheKeyResolver.resolve(ArgumentMatchers.any())).thenAnswer((Answer<String>) invocation -> {
79              final URI uri = invocation.getArgument(0);
80              return HttpCacheSupport.normalize(uri).toASCIIString();
81          });
82  
83          host = new HttpHost("foo.example.com");
84          impl = new DefaultCacheInvalidator();
85      }
86  
87      // Tests
88      @Test
89      public void testInvalidatesRequestsThatArentGETorHEAD() throws Exception {
90          final HttpRequest request = new BasicHttpRequest("POST","/path");
91          final String key = "http://foo.example.com:80/path";
92          final Map<String,String> variantMap = new HashMap<>();
93          cacheEntryHasVariantMap(variantMap);
94  
95          cacheReturnsEntryForUri(key);
96  
97          impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
98  
99          verify(mockEntry).getVariantMap();
100         verify(mockStorage).getEntry(key);
101         verify(mockStorage).removeEntry(key);
102     }
103 
104     @Test
105     public void testInvalidatesUrisInContentLocationHeadersOnPUTs() throws Exception {
106         final HttpRequest request = new BasicHttpRequest("PUT","/");
107         request.setHeader("Content-Length","128");
108 
109         final String contentLocation = "http://foo.example.com/content";
110         request.setHeader("Content-Location", contentLocation);
111 
112         final URI uri = new URI("http://foo.example.com:80/");
113         final String key = uri.toASCIIString();
114         cacheEntryHasVariantMap(new HashMap<>());
115 
116         cacheReturnsEntryForUri(key);
117 
118         impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
119 
120         verify(mockEntry).getVariantMap();
121         verify(mockStorage).getEntry(key);
122         verify(mockStorage).removeEntry(key);
123         verify(mockStorage).removeEntry("http://foo.example.com:80/content");
124     }
125 
126     @Test
127     public void testInvalidatesUrisInLocationHeadersOnPUTs() throws Exception {
128         final HttpRequest request = new BasicHttpRequest("PUT","/");
129         request.setHeader("Content-Length","128");
130 
131         final String contentLocation = "http://foo.example.com/content";
132         request.setHeader("Location",contentLocation);
133 
134         final URI uri = new URI("http://foo.example.com:80/");
135         final String key = uri.toASCIIString();
136         cacheEntryHasVariantMap(new HashMap<>());
137 
138         cacheReturnsEntryForUri(key);
139 
140         impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
141 
142         verify(mockEntry).getVariantMap();
143         verify(mockStorage).getEntry(key);
144         verify(mockStorage).removeEntry(key);
145         verify(mockStorage).removeEntry("http://foo.example.com:80/content");
146     }
147 
148     @Test
149     public void testInvalidatesRelativeUrisInContentLocationHeadersOnPUTs() throws Exception {
150         final HttpRequest request = new BasicHttpRequest("PUT","/");
151         request.setHeader("Content-Length","128");
152 
153         final String relativePath = "/content";
154         request.setHeader("Content-Location",relativePath);
155 
156         final URI uri = new URI("http://foo.example.com:80/");
157         final String key = uri.toASCIIString();
158         cacheEntryHasVariantMap(new HashMap<>());
159 
160         cacheReturnsEntryForUri(key);
161 
162         impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
163 
164         verify(mockEntry).getVariantMap();
165         verify(mockStorage).getEntry(key);
166         verify(mockStorage).removeEntry(key);
167         verify(mockStorage).removeEntry("http://foo.example.com:80/content");
168     }
169 
170     @Test
171     public void testDoesNotInvalidateUrisInContentLocationHeadersOnPUTsToDifferentHosts() throws Exception {
172         final HttpRequest request = new BasicHttpRequest("PUT","/");
173         request.setHeader("Content-Length","128");
174 
175         final String contentLocation = "http://bar.example.com/content";
176         request.setHeader("Content-Location",contentLocation);
177 
178         final URI uri = new URI("http://foo.example.com:80/");
179         final String key = uri.toASCIIString();
180         cacheEntryHasVariantMap(new HashMap<>());
181 
182         cacheReturnsEntryForUri(key);
183 
184         impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
185 
186         verify(mockEntry).getVariantMap();
187         verify(mockStorage).getEntry(key);
188         verify(mockStorage).removeEntry(key);
189     }
190 
191     @Test
192     public void testDoesNotInvalidateGETRequest() throws Exception {
193         final HttpRequest request = new BasicHttpRequest("GET","/");
194         impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
195 
196         verify(mockStorage).getEntry("http://foo.example.com:80/");
197         verifyNoMoreInteractions(mockStorage);
198     }
199 
200     @Test
201     public void testDoesNotInvalidateHEADRequest() throws Exception {
202         final HttpRequest request = new BasicHttpRequest("HEAD","/");
203         impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
204 
205         verify(mockStorage).getEntry("http://foo.example.com:80/");
206         verifyNoMoreInteractions(mockStorage);
207     }
208 
209     @Test
210     public void testInvalidatesHEADCacheEntryIfSubsequentGETRequestsAreMadeToTheSameURI() throws Exception {
211         final URI uri = new URI("http://foo.example.com:80/");
212         final String key = uri.toASCIIString();
213         final HttpRequest request = new BasicHttpRequest("GET", uri);
214 
215         cacheEntryisForMethod("HEAD");
216         cacheEntryHasVariantMap(new HashMap<>());
217         cacheReturnsEntryForUri(key);
218 
219         impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
220 
221         verify(mockEntry).getRequestMethod();
222         verify(mockEntry).getVariantMap();
223         verify(mockStorage).getEntry(key);
224         verify(mockStorage).removeEntry(key);
225     }
226 
227     @Test
228     public void testInvalidatesVariantHEADCacheEntriesIfSubsequentGETRequestsAreMadeToTheSameURI() throws Exception {
229         final URI uri = new URI("http://foo.example.com:80/");
230         final String key = uri.toASCIIString();
231         final HttpRequest request = new BasicHttpRequest("GET", uri);
232         final String theVariantKey = "{Accept-Encoding=gzip%2Cdeflate&User-Agent=Apache-HttpClient}";
233         final String theVariantURI = "{Accept-Encoding=gzip%2Cdeflate&User-Agent=Apache-HttpClient}http://foo.example.com:80/";
234         final Map<String, String> variants = HttpTestUtils.makeDefaultVariantMap(theVariantKey, theVariantURI);
235 
236         cacheEntryisForMethod("HEAD");
237         cacheEntryHasVariantMap(variants);
238         cacheReturnsEntryForUri(key);
239 
240         impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
241 
242         verify(mockEntry).getRequestMethod();
243         verify(mockEntry).getVariantMap();
244         verify(mockStorage).getEntry(key);
245         verify(mockStorage).removeEntry(key);
246         verify(mockStorage).removeEntry(theVariantURI);
247     }
248 
249     @Test
250     public void testDoesNotInvalidateHEADCacheEntry() throws Exception {
251         final URI uri = new URI("http://foo.example.com:80/");
252         final String key = uri.toASCIIString();
253         final HttpRequest request = new BasicHttpRequest("HEAD", uri);
254 
255         cacheReturnsEntryForUri(key);
256 
257         impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
258 
259         verify(mockStorage).getEntry(key);
260         verifyNoMoreInteractions(mockStorage);
261     }
262 
263     @Test
264     public void testDoesNotInvalidateHEADCacheEntryIfSubsequentHEADRequestsAreMadeToTheSameURI() throws Exception {
265         final URI uri = new URI("http://foo.example.com:80/");
266         final String key = uri.toASCIIString();
267         final HttpRequest request = new BasicHttpRequest("HEAD", uri);
268 
269         cacheReturnsEntryForUri(key);
270 
271         impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
272 
273         verify(mockStorage).getEntry(key);
274         verifyNoMoreInteractions(mockStorage);
275     }
276 
277     @Test
278     public void testDoesNotInvalidateGETCacheEntryIfSubsequentGETRequestsAreMadeToTheSameURI() throws Exception {
279         final URI uri = new URI("http://foo.example.com:80/");
280         final String key = uri.toASCIIString();
281         final HttpRequest request = new BasicHttpRequest("GET", uri);
282 
283         cacheEntryisForMethod("GET");
284         cacheReturnsEntryForUri(key);
285 
286         impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
287 
288         verify(mockEntry).getRequestMethod();
289         verify(mockStorage).getEntry(key);
290         verifyNoMoreInteractions(mockStorage);
291     }
292 
293     @Test
294     public void testDoesNotInvalidateRequestsWithClientCacheControlHeaders() throws Exception {
295         final HttpRequest request = new BasicHttpRequest("GET","/");
296         request.setHeader("Cache-Control","no-cache");
297 
298         impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
299 
300         verify(mockStorage).getEntry("http://foo.example.com:80/");
301         verifyNoMoreInteractions(mockStorage);
302     }
303 
304     @Test
305     public void testDoesNotInvalidateRequestsWithClientPragmaHeaders() throws Exception {
306         final HttpRequest request = new BasicHttpRequest("GET","/");
307         request.setHeader("Pragma","no-cache");
308 
309         impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
310 
311         verify(mockStorage).getEntry("http://foo.example.com:80/");
312         verifyNoMoreInteractions(mockStorage);
313     }
314 
315     @Test
316     public void testVariantURIsAreFlushedAlso() throws Exception {
317         final HttpRequest request = new BasicHttpRequest("POST","/");
318         final URI uri = new URI("http://foo.example.com:80/");
319         final String key = uri.toASCIIString();
320         final String variantUri = "theVariantURI";
321         final Map<String,String> mapOfURIs = HttpTestUtils.makeDefaultVariantMap(variantUri, variantUri);
322 
323         cacheReturnsEntryForUri(key);
324         cacheEntryHasVariantMap(mapOfURIs);
325 
326         impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
327 
328         verify(mockStorage).getEntry(key);
329         verify(mockEntry).getVariantMap();
330         verify(mockStorage).removeEntry(variantUri);
331         verify(mockStorage).removeEntry(key);
332     }
333 
334     @Test
335     public void doesNotFlushForResponsesWithoutContentLocation() throws Exception {
336         final HttpRequest request = new BasicHttpRequest("POST","/");
337         final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
338         impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
339 
340         verifyNoMoreInteractions(mockStorage);
341     }
342 
343     @Test
344     public void flushesEntryIfFresherAndSpecifiedByContentLocation() throws Exception {
345         final HttpRequest request = new BasicHttpRequest("GET", "/");
346         final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
347         response.setHeader("ETag","\"new-etag\"");
348         response.setHeader("Date", DateUtils.formatStandardDate(now));
349         final String key = "http://foo.example.com:80/bar";
350         response.setHeader("Content-Location", key);
351 
352         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
353            new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
354            new BasicHeader("ETag", "\"old-etag\"")
355         });
356 
357         when(mockStorage.getEntry(key)).thenReturn(entry);
358 
359         impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
360 
361         verify(mockStorage).getEntry(key);
362         verify(mockStorage).removeEntry(key);
363     }
364 
365     @Test
366     public void flushesEntryIfFresherAndSpecifiedByLocation() throws Exception {
367         final HttpRequest request = new BasicHttpRequest("GET", "/");
368         final HttpResponse response = new BasicHttpResponse(201);
369         response.setHeader("ETag","\"new-etag\"");
370         response.setHeader("Date", DateUtils.formatStandardDate(now));
371         final String key = "http://foo.example.com:80/bar";
372         response.setHeader("Location", key);
373 
374         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
375            new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
376            new BasicHeader("ETag", "\"old-etag\"")
377         });
378 
379         when(mockStorage.getEntry(key)).thenReturn(entry);
380 
381         impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
382 
383         verify(mockStorage).getEntry(key);
384         verify(mockStorage).removeEntry(key);
385     }
386 
387     @Test
388     public void doesNotFlushEntryForUnsuccessfulResponse() throws Exception {
389         final HttpRequest request = new BasicHttpRequest("GET", "/");
390         final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_BAD_REQUEST, "Bad Request");
391         response.setHeader("ETag","\"new-etag\"");
392         response.setHeader("Date", DateUtils.formatStandardDate(now));
393         final String key = "http://foo.example.com:80/bar";
394         response.setHeader("Content-Location", key);
395 
396         impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
397 
398         verifyNoMoreInteractions(mockStorage);
399     }
400 
401     @Test
402     public void flushesEntryIfFresherAndSpecifiedByNonCanonicalContentLocation() throws Exception {
403         final HttpRequest request = new BasicHttpRequest("GET", "/");
404         final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
405         response.setHeader("ETag","\"new-etag\"");
406         response.setHeader("Date", DateUtils.formatStandardDate(now));
407         final String cacheKey = "http://foo.example.com:80/bar";
408         response.setHeader("Content-Location", "http://foo.example.com/bar");
409 
410         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
411            new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
412            new BasicHeader("ETag", "\"old-etag\"")
413         });
414 
415         when(mockStorage.getEntry(cacheKey)).thenReturn(entry);
416 
417         impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
418 
419         verify(mockStorage).getEntry(cacheKey);
420         verify(mockStorage).removeEntry(cacheKey);
421     }
422 
423     @Test
424     public void flushesEntryIfFresherAndSpecifiedByRelativeContentLocation() throws Exception {
425         final HttpRequest request = new BasicHttpRequest("GET", "/");
426         final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
427         response.setHeader("ETag","\"new-etag\"");
428         response.setHeader("Date", DateUtils.formatStandardDate(now));
429         final String cacheKey = "http://foo.example.com:80/bar";
430         response.setHeader("Content-Location", "/bar");
431 
432         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
433            new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
434            new BasicHeader("ETag", "\"old-etag\"")
435         });
436 
437         when(mockStorage.getEntry(cacheKey)).thenReturn(entry);
438 
439         impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
440 
441         verify(mockStorage).getEntry(cacheKey);
442         verify(mockStorage).removeEntry(cacheKey);
443     }
444 
445     @Test
446     public void doesNotFlushEntryIfContentLocationFromDifferentHost() throws Exception {
447         final HttpRequest request = new BasicHttpRequest("GET", "/");
448         final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
449         response.setHeader("ETag","\"new-etag\"");
450         response.setHeader("Date", DateUtils.formatStandardDate(now));
451         final String cacheKey = "http://baz.example.com:80/bar";
452         response.setHeader("Content-Location", cacheKey);
453 
454         impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
455 
456         verifyNoMoreInteractions(mockStorage);
457     }
458 
459     @Test
460     public void doesNotFlushEntrySpecifiedByContentLocationIfEtagsMatch() throws Exception {
461         final HttpRequest request = new BasicHttpRequest("GET", "/");
462         final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
463         response.setHeader("ETag","\"same-etag\"");
464         response.setHeader("Date", DateUtils.formatStandardDate(now));
465         final String key = "http://foo.example.com:80/bar";
466         response.setHeader("Content-Location", key);
467 
468         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
469            new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
470            new BasicHeader("ETag", "\"same-etag\"")
471         });
472 
473         when(mockStorage.getEntry(key)).thenReturn(entry);
474         impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
475 
476         verify(mockStorage).getEntry(key);
477         verifyNoMoreInteractions(mockStorage);
478     }
479 
480     @Test
481     public void doesNotFlushEntrySpecifiedByContentLocationIfOlder() throws Exception {
482         final HttpRequest request = new BasicHttpRequest("GET", "/");
483         final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
484         response.setHeader("ETag","\"new-etag\"");
485         response.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
486         final String key = "http://foo.example.com:80/bar";
487         response.setHeader("Content-Location", key);
488 
489         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
490            new BasicHeader("Date", DateUtils.formatStandardDate(now)),
491            new BasicHeader("ETag", "\"old-etag\"")
492         });
493 
494         when(mockStorage.getEntry(key)).thenReturn(entry);
495 
496         impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
497 
498         verify(mockStorage).getEntry(key);
499         verifyNoMoreInteractions(mockStorage);
500     }
501 
502     @Test
503     public void doesNotFlushEntryIfNotInCache() throws Exception {
504         final HttpRequest request = new BasicHttpRequest("GET", "/");
505         final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
506         response.setHeader("ETag","\"new-etag\"");
507         response.setHeader("Date", DateUtils.formatStandardDate(now));
508         final String key = "http://foo.example.com:80/bar";
509         response.setHeader("Content-Location", key);
510 
511         when(mockStorage.getEntry(key)).thenReturn(null);
512 
513         impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
514 
515         verify(mockStorage).getEntry(key);
516         verifyNoMoreInteractions(mockStorage);
517     }
518 
519     @Test
520     public void doesNotFlushEntrySpecifiedByContentLocationIfResponseHasNoEtag() throws Exception {
521         final HttpRequest request = new BasicHttpRequest("GET", "/");
522         final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
523         response.removeHeaders("ETag");
524         response.setHeader("Date", DateUtils.formatStandardDate(now));
525         final String key = "http://foo.example.com:80/bar";
526         response.setHeader("Content-Location", key);
527 
528         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
529            new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
530            new BasicHeader("ETag", "\"old-etag\"")
531         });
532 
533         when(mockStorage.getEntry(key)).thenReturn(entry);
534 
535         impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
536 
537         verify(mockStorage).getEntry(key);
538         verifyNoMoreInteractions(mockStorage);
539     }
540 
541     @Test
542     public void doesNotFlushEntrySpecifiedByContentLocationIfEntryHasNoEtag() throws Exception {
543         final HttpRequest request = new BasicHttpRequest("GET", "/");
544         final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
545         response.setHeader("ETag", "\"some-etag\"");
546         response.setHeader("Date", DateUtils.formatStandardDate(now));
547         final String key = "http://foo.example.com:80/bar";
548         response.setHeader("Content-Location", key);
549 
550         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
551            new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
552         });
553 
554         when(mockStorage.getEntry(key)).thenReturn(entry);
555 
556         impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
557 
558         verify(mockStorage).getEntry(key);
559         verifyNoMoreInteractions(mockStorage);
560     }
561 
562     @Test
563     public void flushesEntrySpecifiedByContentLocationIfResponseHasNoDate() throws Exception {
564         final HttpRequest request = new BasicHttpRequest("GET", "/");
565         final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
566         response.setHeader("ETag", "\"new-etag\"");
567         response.removeHeaders("Date");
568         final String key = "http://foo.example.com:80/bar";
569         response.setHeader("Content-Location", key);
570 
571         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
572                 new BasicHeader("ETag", "\"old-etag\""),
573                 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
574         });
575 
576         when(mockStorage.getEntry(key)).thenReturn(entry);
577 
578         impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
579 
580         verify(mockStorage).getEntry(key);
581         verify(mockStorage).removeEntry(key);
582         verifyNoMoreInteractions(mockStorage);
583     }
584 
585     @Test
586     public void flushesEntrySpecifiedByContentLocationIfEntryHasNoDate() throws Exception {
587         final HttpRequest request = new BasicHttpRequest("GET", "/");
588         final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
589         response.setHeader("ETag","\"new-etag\"");
590         response.setHeader("Date", DateUtils.formatStandardDate(now));
591         final String key = "http://foo.example.com:80/bar";
592         response.setHeader("Content-Location", key);
593 
594         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
595            new BasicHeader("ETag", "\"old-etag\"")
596         });
597 
598         when(mockStorage.getEntry(key)).thenReturn(entry);
599 
600         impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
601 
602         verify(mockStorage).getEntry(key);
603         verify(mockStorage).removeEntry(key);
604         verifyNoMoreInteractions(mockStorage);
605     }
606 
607     @Test
608     public void flushesEntrySpecifiedByContentLocationIfResponseHasMalformedDate() throws Exception {
609         final HttpRequest request = new BasicHttpRequest("GET", "/");
610         final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
611         response.setHeader("ETag","\"new-etag\"");
612         response.setHeader("Date", "blarg");
613         final String key = "http://foo.example.com:80/bar";
614         response.setHeader("Content-Location", key);
615 
616         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
617                 new BasicHeader("ETag", "\"old-etag\""),
618                 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))
619         });
620 
621         when(mockStorage.getEntry(key)).thenReturn(entry);
622 
623         impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
624 
625         verify(mockStorage).getEntry(key);
626         verify(mockStorage).removeEntry(key);
627         verifyNoMoreInteractions(mockStorage);
628     }
629 
630     @Test
631     public void flushesEntrySpecifiedByContentLocationIfEntryHasMalformedDate() throws Exception {
632         final HttpRequest request = new BasicHttpRequest("GET", "/");
633         final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
634         response.setHeader("ETag","\"new-etag\"");
635         response.setHeader("Date", DateUtils.formatStandardDate(now));
636         final String key = "http://foo.example.com:80/bar";
637         response.setHeader("Content-Location", key);
638 
639         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
640                 new BasicHeader("ETag", "\"old-etag\""),
641                 new BasicHeader("Date", "foo")
642         });
643 
644         when(mockStorage.getEntry(key)).thenReturn(entry);
645 
646         impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
647 
648         verify(mockStorage).getEntry(key);
649         verify(mockStorage).removeEntry(key);
650         verifyNoMoreInteractions(mockStorage);
651     }
652 
653 
654     // Expectations
655     private void cacheEntryHasVariantMap(final Map<String,String> variantMap) {
656         when(mockEntry.getVariantMap()).thenReturn(variantMap);
657     }
658 
659     private void cacheReturnsEntryForUri(final String key) throws IOException {
660         when(mockStorage.getEntry(key)).thenReturn(mockEntry);
661     }
662 
663     private void cacheEntryisForMethod(final String httpMethod) {
664         when(mockEntry.getRequestMethod()).thenReturn(httpMethod);
665     }
666 }