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