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.junit.Assert.assertEquals;
30 import static org.junit.Assert.assertFalse;
31 import static org.junit.Assert.assertSame;
32 import static org.junit.Assert.assertTrue;
33
34 import java.util.Date;
35
36 import org.apache.hc.client5.http.cache.HttpCacheEntry;
37 import org.apache.hc.client5.http.utils.DateUtils;
38 import org.apache.hc.core5.http.Header;
39 import org.apache.hc.core5.http.HttpHeaders;
40 import org.apache.hc.core5.http.HttpRequest;
41 import org.apache.hc.core5.http.message.BasicHeader;
42 import org.apache.hc.core5.http.message.BasicHttpRequest;
43 import org.apache.hc.core5.util.TimeValue;
44 import org.junit.Before;
45 import org.junit.Test;
46
47 public class TestCacheValidityPolicy {
48
49 private CacheValidityPolicy impl;
50 private Date now;
51 private Date oneSecondAgo;
52 private Date sixSecondsAgo;
53 private Date tenSecondsAgo;
54 private Date elevenSecondsAgo;
55
56 @Before
57 public void setUp() {
58 impl = new CacheValidityPolicy();
59 now = new Date();
60 oneSecondAgo = new Date(now.getTime() - 1 * 1000L);
61 sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
62 tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
63 elevenSecondsAgo = new Date(now.getTime() - 11 * 1000L);
64 }
65
66 @Test
67 public void testApparentAgeIsMaxIntIfDateHeaderNotPresent() {
68 final Header[] headers = {
69 new BasicHeader("Server", "MockServer/1.0")
70 };
71 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
72 assertEquals(CacheValidityPolicy.MAX_AGE, impl.getApparentAge(entry));
73 }
74
75 @Test
76 public void testApparentAgeIsResponseReceivedTimeLessDateHeader() {
77 final Header[] headers = new Header[] { new BasicHeader("Date", DateUtils
78 .formatDate(tenSecondsAgo)) };
79
80 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, sixSecondsAgo, headers);
81 assertEquals(TimeValue.ofSeconds(4), impl.getApparentAge(entry));
82 }
83
84 @Test
85 public void testNegativeApparentAgeIsBroughtUpToZero() {
86 final Header[] headers = new Header[] { new BasicHeader("Date", DateUtils
87 .formatDate(sixSecondsAgo)) };
88 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, tenSecondsAgo, headers);
89 assertEquals(TimeValue.ofSeconds(0), impl.getApparentAge(entry));
90 }
91
92 @Test
93 public void testCorrectedReceivedAgeIsAgeHeaderIfLarger() {
94 final Header[] headers = new Header[] { new BasicHeader("Age", "10"), };
95 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
96 impl = new CacheValidityPolicy() {
97 @Override
98 protected TimeValue getApparentAge(final HttpCacheEntry ent) {
99 return TimeValue.ofSeconds(6);
100 }
101 };
102 assertEquals(TimeValue.ofSeconds(10), impl.getCorrectedReceivedAge(entry));
103 }
104
105 @Test
106 public void testCorrectedReceivedAgeIsApparentAgeIfLarger() {
107 final Header[] headers = new Header[] { new BasicHeader("Age", "6"), };
108 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
109 impl = new CacheValidityPolicy() {
110 @Override
111 protected TimeValue getApparentAge(final HttpCacheEntry ent) {
112 return TimeValue.ofSeconds(10);
113 }
114 };
115 assertEquals(TimeValue.ofSeconds(10), impl.getCorrectedReceivedAge(entry));
116 }
117
118 @Test
119 public void testResponseDelayIsDifferenceBetweenResponseAndRequestTimes() {
120 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, sixSecondsAgo);
121 assertEquals(TimeValue.ofSeconds(4), impl.getResponseDelay(entry));
122 }
123
124 @Test
125 public void testCorrectedInitialAgeIsCorrectedReceivedAgePlusResponseDelay() {
126 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
127 impl = new CacheValidityPolicy() {
128 @Override
129 protected TimeValue getCorrectedReceivedAge(final HttpCacheEntry ent) {
130 return TimeValue.ofSeconds(7);
131 }
132
133 @Override
134 protected TimeValue getResponseDelay(final HttpCacheEntry ent) {
135 return TimeValue.ofSeconds(13);
136 }
137 };
138 assertEquals(TimeValue.ofSeconds(20), impl.getCorrectedInitialAge(entry));
139 }
140
141 @Test
142 public void testResidentTimeSecondsIsTimeSinceResponseTime() {
143 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, sixSecondsAgo);
144 assertEquals(TimeValue.ofSeconds(6), impl.getResidentTime(entry, now));
145 }
146
147 @Test
148 public void testCurrentAgeIsCorrectedInitialAgePlusResidentTime() {
149 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
150 impl = new CacheValidityPolicy() {
151 @Override
152 protected TimeValue getCorrectedInitialAge(final HttpCacheEntry ent) {
153 return TimeValue.ofSeconds(11);
154 }
155 @Override
156 protected TimeValue getResidentTime(final HttpCacheEntry ent, final Date d) {
157 return TimeValue.ofSeconds(17);
158 }
159 };
160 assertEquals(TimeValue.ofSeconds(28), impl.getCurrentAge(entry, new Date()));
161 }
162
163 @Test
164 public void testFreshnessLifetimeIsSMaxAgeIfPresent() {
165 final Header[] headers = new Header[] { new BasicHeader("Cache-Control", "s-maxage=10") };
166 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
167 assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(entry));
168 }
169
170 @Test
171 public void testFreshnessLifetimeIsMaxAgeIfPresent() {
172 final Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10") };
173 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
174 assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(entry));
175 }
176
177 @Test
178 public void testFreshnessLifetimeIsMostRestrictiveOfMaxAgeAndSMaxAge() {
179 Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10"),
180 new BasicHeader("Cache-Control", "s-maxage=20") };
181 HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
182 assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(entry));
183
184 headers = new Header[] { new BasicHeader("Cache-Control", "max-age=20"),
185 new BasicHeader("Cache-Control", "s-maxage=10") };
186 entry = HttpTestUtils.makeCacheEntry(headers);
187 assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(entry));
188 }
189
190 @Test
191 public void testFreshnessLifetimeIsMaxAgeEvenIfExpiresIsPresent() {
192 final Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10"),
193 new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
194 new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) };
195
196 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
197 assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(entry));
198 }
199
200 @Test
201 public void testFreshnessLifetimeIsSMaxAgeEvenIfExpiresIsPresent() {
202 final Header[] headers = new Header[] { new BasicHeader("Cache-Control", "s-maxage=10"),
203 new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
204 new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) };
205
206 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
207 assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(entry));
208 }
209
210 @Test
211 public void testFreshnessLifetimeIsFromExpiresHeaderIfNoMaxAge() {
212 final Header[] headers = new Header[] {
213 new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
214 new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) };
215
216 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
217 assertEquals(TimeValue.ofSeconds(4), impl.getFreshnessLifetime(entry));
218 }
219
220 @Test
221 public void testHeuristicFreshnessLifetime() {
222 final Header[] headers = new Header[] {
223 new BasicHeader("Date", DateUtils.formatDate(oneSecondAgo)),
224 new BasicHeader("Last-Modified", DateUtils.formatDate(elevenSecondsAgo))
225 };
226 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
227 assertEquals(TimeValue.ofSeconds(1), impl.getHeuristicFreshnessLifetime(entry, 0.1f, TimeValue.ZERO_MILLISECONDS));
228 }
229
230 @Test
231 public void testHeuristicFreshnessLifetimeDefaultsProperly() {
232 final TimeValue defaultFreshness = TimeValue.ofSeconds(10);
233 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
234 assertEquals(defaultFreshness, impl.getHeuristicFreshnessLifetime(entry, 0.1f, defaultFreshness));
235 }
236
237 @Test
238 public void testHeuristicFreshnessLifetimeIsNonNegative() {
239 final Header[] headers = new Header[] {
240 new BasicHeader("Date", DateUtils.formatDate(elevenSecondsAgo)),
241 new BasicHeader("Last-Modified", DateUtils.formatDate(oneSecondAgo))
242 };
243
244 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
245 assertTrue(TimeValue.isNonNegative(impl.getHeuristicFreshnessLifetime(entry, 0.1f, TimeValue.ofSeconds(10))));
246 }
247
248 @Test
249 public void testResponseIsFreshIfFreshnessLifetimeExceedsCurrentAge() {
250 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
251 impl = new CacheValidityPolicy() {
252 @Override
253 public TimeValue getCurrentAge(final HttpCacheEntry e, final Date d) {
254 assertSame(entry, e);
255 assertEquals(now, d);
256 return TimeValue.ofSeconds(6);
257 }
258 @Override
259 public TimeValue getFreshnessLifetime(final HttpCacheEntry e) {
260 assertSame(entry, e);
261 return TimeValue.ofSeconds(10);
262 }
263 };
264 assertTrue(impl.isResponseFresh(entry, now));
265 }
266
267 @Test
268 public void testResponseIsNotFreshIfFreshnessLifetimeEqualsCurrentAge() {
269 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
270 impl = new CacheValidityPolicy() {
271 @Override
272 public TimeValue getCurrentAge(final HttpCacheEntry e, final Date d) {
273 assertEquals(now, d);
274 assertSame(entry, e);
275 return TimeValue.ofSeconds(6);
276 }
277 @Override
278 public TimeValue getFreshnessLifetime(final HttpCacheEntry e) {
279 assertSame(entry, e);
280 return TimeValue.ofSeconds(6);
281 }
282 };
283 assertFalse(impl.isResponseFresh(entry, now));
284 }
285
286 @Test
287 public void testResponseIsNotFreshIfCurrentAgeExceedsFreshnessLifetime() {
288 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
289 impl = new CacheValidityPolicy() {
290 @Override
291 public TimeValue getCurrentAge(final HttpCacheEntry e, final Date d) {
292 assertEquals(now, d);
293 assertSame(entry, e);
294 return TimeValue.ofSeconds(10);
295 }
296 @Override
297 public TimeValue getFreshnessLifetime(final HttpCacheEntry e) {
298 assertSame(entry, e);
299 return TimeValue.ofSeconds(6);
300 }
301 };
302 assertFalse(impl.isResponseFresh(entry, now));
303 }
304
305 @Test
306 public void testCacheEntryIsRevalidatableIfHeadersIncludeETag() {
307 final Header[] headers = {
308 new BasicHeader("Expires", DateUtils.formatDate(new Date())),
309 new BasicHeader("ETag", "somevalue")};
310 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
311 assertTrue(impl.isRevalidatable(entry));
312 }
313
314 @Test
315 public void testCacheEntryIsRevalidatableIfHeadersIncludeLastModifiedDate() {
316 final Header[] headers = {
317 new BasicHeader("Expires", DateUtils.formatDate(new Date())),
318 new BasicHeader("Last-Modified", DateUtils.formatDate(new Date())) };
319 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
320 assertTrue(impl.isRevalidatable(entry));
321 }
322
323 @Test
324 public void testCacheEntryIsNotRevalidatableIfNoAppropriateHeaders() {
325 final Header[] headers = {
326 new BasicHeader("Expires", DateUtils.formatDate(new Date())),
327 new BasicHeader("Cache-Control", "public") };
328 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
329 assertFalse(impl.isRevalidatable(entry));
330 }
331
332 @Test
333 public void testMissingContentLengthDoesntInvalidateEntry() {
334 final int contentLength = 128;
335 final Header[] headers = {};
336 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers, HttpTestUtils.getRandomBytes(contentLength));
337 assertTrue(impl.contentLengthHeaderMatchesActualLength(entry));
338 }
339
340 @Test
341 public void testCorrectContentLengthDoesntInvalidateEntry() {
342 final int contentLength = 128;
343 final Header[] headers = { new BasicHeader(HttpHeaders.CONTENT_LENGTH, Integer.toString(contentLength)) };
344 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers, HttpTestUtils.getRandomBytes(contentLength));
345 assertTrue(impl.contentLengthHeaderMatchesActualLength(entry));
346 }
347
348 @Test
349 public void testWrongContentLengthInvalidatesEntry() {
350 final int contentLength = 128;
351 final Header[] headers = {new BasicHeader(HttpHeaders.CONTENT_LENGTH, Integer.toString(contentLength+1))};
352 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers, HttpTestUtils.getRandomBytes(contentLength));
353 assertFalse(impl.contentLengthHeaderMatchesActualLength(entry));
354 }
355
356 @Test
357 public void testNullResourceInvalidatesEntry() {
358 final int contentLength = 128;
359 final Header[] headers = {new BasicHeader(HttpHeaders.CONTENT_LENGTH, Integer.toString(contentLength))};
360 final HttpCacheEntry entry = HttpTestUtils.makeHeadCacheEntry(headers);
361 assertFalse(impl.contentLengthHeaderMatchesActualLength(entry));
362 }
363
364 @Test
365 public void testNegativeAgeHeaderValueReturnsMaxAge() {
366 final Header[] headers = new Header[] { new BasicHeader("Age", "-100") };
367 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
368
369 assertEquals(CacheValidityPolicy.MAX_AGE.toSeconds(), impl.getAgeValue(entry));
370 }
371
372 @Test
373 public void testMalformedAgeHeaderValueReturnsMaxAge() {
374 final Header[] headers = new Header[] { new BasicHeader("Age", "asdf") };
375 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
376
377 assertEquals(CacheValidityPolicy.MAX_AGE.toSeconds(), impl.getAgeValue(entry));
378 }
379
380 @Test
381 public void testMalformedCacheControlMaxAgeHeaderReturnsZero() {
382 final Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=asdf") };
383 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
384
385 assertEquals(0L, impl.getMaxAge(entry));
386 }
387
388 @Test
389 public void testMustRevalidateIsFalseIfDirectiveNotPresent() {
390 final Header[] headers = new Header[] { new BasicHeader("Cache-Control","public") };
391 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
392 assertFalse(impl.mustRevalidate(entry));
393 }
394
395 @Test
396 public void testMustRevalidateIsTrueWhenDirectiveIsPresent() {
397 final Header[] headers = new Header[] { new BasicHeader("Cache-Control","public, must-revalidate") };
398 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
399 assertTrue(impl.mustRevalidate(entry));
400 }
401
402 @Test
403 public void testProxyRevalidateIsFalseIfDirectiveNotPresent() {
404 final Header[] headers = new Header[] { new BasicHeader("Cache-Control","public") };
405 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
406 assertFalse(impl.proxyRevalidate(entry));
407 }
408
409 @Test
410 public void testProxyRevalidateIsTrueWhenDirectiveIsPresent() {
411 final Header[] headers = new Header[] { new BasicHeader("Cache-Control","public, proxy-revalidate") };
412 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
413 assertTrue(impl.proxyRevalidate(entry));
414 }
415
416 @Test
417 public void testMayReturnStaleIfErrorInResponseIsTrueWithinStaleness(){
418 final Header[] headers = new Header[] {
419 new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
420 new BasicHeader("Cache-Control", "max-age=5, stale-if-error=15")
421 };
422 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
423 final HttpRequest req = new BasicHttpRequest("GET","/");
424 assertTrue(impl.mayReturnStaleIfError(req, entry, now));
425 }
426
427 @Test
428 public void testMayReturnStaleIfErrorInRequestIsTrueWithinStaleness(){
429 final Header[] headers = new Header[] {
430 new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
431 new BasicHeader("Cache-Control", "max-age=5")
432 };
433 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
434 final HttpRequest req = new BasicHttpRequest("GET","/");
435 req.setHeader("Cache-Control","stale-if-error=15");
436 assertTrue(impl.mayReturnStaleIfError(req, entry, now));
437 }
438
439 @Test
440 public void testMayNotReturnStaleIfErrorInResponseAndAfterResponseWindow(){
441 final Header[] headers = new Header[] {
442 new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
443 new BasicHeader("Cache-Control", "max-age=5, stale-if-error=1")
444 };
445 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
446 final HttpRequest req = new BasicHttpRequest("GET","/");
447 assertFalse(impl.mayReturnStaleIfError(req, entry, now));
448 }
449
450 @Test
451 public void testMayNotReturnStaleIfErrorInResponseAndAfterRequestWindow(){
452 final Header[] headers = new Header[] {
453 new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
454 new BasicHeader("Cache-Control", "max-age=5")
455 };
456 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
457 final HttpRequest req = new BasicHttpRequest("GET","/");
458 req.setHeader("Cache-Control","stale-if-error=1");
459 assertFalse(impl.mayReturnStaleIfError(req, entry, now));
460 }
461
462 @Test
463 public void testMayReturnStaleWhileRevalidatingIsFalseWhenDirectiveIsAbsent() {
464 final Header[] headers = new Header[] { new BasicHeader("Cache-control", "public") };
465 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
466
467 assertFalse(impl.mayReturnStaleWhileRevalidating(entry, now));
468 }
469
470 @Test
471 public void testMayReturnStaleWhileRevalidatingIsTrueWhenWithinStaleness() {
472 final Header[] headers = new Header[] {
473 new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
474 new BasicHeader("Cache-Control", "max-age=5, stale-while-revalidate=15")
475 };
476 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
477
478 assertTrue(impl.mayReturnStaleWhileRevalidating(entry, now));
479 }
480
481 @Test
482 public void testMayReturnStaleWhileRevalidatingIsFalseWhenPastStaleness() {
483 final Date twentyFiveSecondsAgo = new Date(now.getTime() - 25 * 1000L);
484 final Header[] headers = new Header[] {
485 new BasicHeader("Date", DateUtils.formatDate(twentyFiveSecondsAgo)),
486 new BasicHeader("Cache-Control", "max-age=5, stale-while-revalidate=15")
487 };
488 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
489
490 assertFalse(impl.mayReturnStaleWhileRevalidating(entry, now));
491 }
492
493 @Test
494 public void testMayReturnStaleWhileRevalidatingIsFalseWhenDirectiveEmpty() {
495 final Header[] headers = new Header[] {
496 new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
497 new BasicHeader("Cache-Control", "max-age=5, stale-while-revalidate=")
498 };
499 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
500
501 assertFalse(impl.mayReturnStaleWhileRevalidating(entry, now));
502 }
503 }