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  
30  import static org.junit.jupiter.api.Assertions.assertEquals;
31  import static org.junit.jupiter.api.Assertions.assertTrue;
32  
33  import java.time.Instant;
34  
35  import org.apache.hc.client5.http.cache.HttpCacheEntry;
36  import org.apache.hc.client5.http.cache.ResponseCacheControl;
37  import org.apache.hc.client5.http.utils.DateUtils;
38  import org.apache.hc.core5.http.Header;
39  import org.apache.hc.core5.http.message.BasicHeader;
40  import org.apache.hc.core5.util.TimeValue;
41  import org.junit.jupiter.api.BeforeEach;
42  import org.junit.jupiter.api.Test;
43  
44  public class TestCacheValidityPolicy {
45  
46      private CacheValidityPolicy impl;
47      private Instant now;
48      private Instant oneSecondAgo;
49      private Instant sixSecondsAgo;
50      private Instant tenSecondsAgo;
51      private Instant elevenSecondsAgo;
52  
53      @BeforeEach
54      public void setUp() {
55          impl = new CacheValidityPolicy();
56          now = Instant.now();
57          oneSecondAgo = now.minusSeconds(1);
58          sixSecondsAgo = now.minusSeconds(6);
59          tenSecondsAgo = now.minusSeconds(10);
60          elevenSecondsAgo = now.minusSeconds(11);
61      }
62  
63      @Test
64      public void testApparentAgeIsMaxIntIfDateHeaderNotPresent() {
65          final Header[] headers = {
66                  new BasicHeader("Server", "MockServer/1.0")
67          };
68          final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
69          assertEquals(CacheSupport.MAX_AGE, impl.getApparentAge(entry));
70      }
71  
72      @Test
73      public void testApparentAgeIsResponseReceivedTimeLessDateHeader() {
74          final Header[] headers = new Header[] { new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)) };
75  
76          final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, sixSecondsAgo, headers);
77          assertEquals(TimeValue.ofSeconds(4), impl.getApparentAge(entry));
78      }
79  
80      @Test
81      public void testNegativeApparentAgeIsBroughtUpToZero() {
82          final Header[] headers = new Header[] { new BasicHeader("Date", DateUtils.formatStandardDate(sixSecondsAgo)) };
83          final HttpCacheEntry entry  = HttpTestUtils.makeCacheEntry(now, tenSecondsAgo, headers);
84          assertEquals(TimeValue.ofSeconds(0), impl.getApparentAge(entry));
85      }
86  
87      @Test
88      public void testCorrectedReceivedAgeIsAgeHeaderIfLarger() {
89          final Header[] headers = new Header[] { new BasicHeader("Age", "10"), };
90          final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
91          impl = new CacheValidityPolicy() {
92              @Override
93              protected TimeValue getApparentAge(final HttpCacheEntry ent) {
94                  return TimeValue.ofSeconds(6);
95              }
96          };
97          assertEquals(TimeValue.ofSeconds(10), impl.getCorrectedAgeValue(entry));
98      }
99  
100     @Test
101     public void testGetCorrectedAgeValue() {
102         final Header[] headers = new Header[] { new BasicHeader("Age", "6"), };
103         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
104         assertEquals(TimeValue.ofSeconds(6), impl.getCorrectedAgeValue(entry));
105     }
106 
107     @Test
108     public void testResponseDelayIsDifferenceBetweenResponseAndRequestTimes() {
109         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, sixSecondsAgo);
110         assertEquals(TimeValue.ofSeconds(4), impl.getResponseDelay(entry));
111     }
112 
113     @Test
114     public void testCorrectedInitialAgeIsCorrectedReceivedAgePlusResponseDelay() {
115         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
116         impl = new CacheValidityPolicy() {
117             @Override
118             protected TimeValue getCorrectedAgeValue(final HttpCacheEntry ent) {
119                 return TimeValue.ofSeconds(7);
120             }
121 
122             @Override
123             protected TimeValue getResponseDelay(final HttpCacheEntry ent) {
124                 return TimeValue.ofSeconds(13);
125             }
126         };
127         assertEquals(TimeValue.ofSeconds(7), impl.getCorrectedInitialAge(entry));
128     }
129 
130     @Test
131     public void testResidentTimeSecondsIsTimeSinceResponseTime() {
132         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, sixSecondsAgo);
133         assertEquals(TimeValue.ofSeconds(6), impl.getResidentTime(entry, now));
134     }
135 
136     @Test
137     public void testCurrentAgeIsCorrectedInitialAgePlusResidentTime() {
138         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
139         impl = new CacheValidityPolicy() {
140             @Override
141             protected TimeValue getCorrectedInitialAge(final HttpCacheEntry ent) {
142                 return TimeValue.ofSeconds(11);
143             }
144             @Override
145             protected TimeValue getResidentTime(final HttpCacheEntry ent, final Instant d) {
146                 return TimeValue.ofSeconds(17);
147             }
148         };
149         assertEquals(TimeValue.ofSeconds(28), impl.getCurrentAge(entry, Instant.now()));
150     }
151 
152     @Test
153     public void testFreshnessLifetimeIsSMaxAgeIfPresent() {
154         final ResponseCacheControl cacheControl = ResponseCacheControl.builder()
155                 .setSharedMaxAge(10)
156                 .setMaxAge(5)
157                 .build();
158         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
159         assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(cacheControl, entry));
160     }
161 
162     @Test
163     public void testSMaxAgeIsIgnoredWhenNotShared() {
164         final CacheConfig cacheConfig = CacheConfig.custom()
165                 .setSharedCache(false)
166                 .build();
167         impl = new CacheValidityPolicy(cacheConfig);
168         final ResponseCacheControl cacheControl = ResponseCacheControl.builder()
169                 .setSharedMaxAge(10)
170                 .setMaxAge(5)
171                 .build();
172         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
173         assertEquals(TimeValue.ofSeconds(5), impl.getFreshnessLifetime(cacheControl, entry));
174     }
175 
176     @Test
177     public void testFreshnessLifetimeIsMaxAgeIfPresent() {
178         final ResponseCacheControl cacheControl = ResponseCacheControl.builder()
179                 .setMaxAge(10)
180                 .build();
181         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
182         assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(cacheControl, entry));
183     }
184 
185     @Test
186     public void testFreshnessLifetimeUsesSharedMaxAgeInSharedCache() {
187         // assuming impl represents a shared cache
188         final ResponseCacheControl cacheControl = ResponseCacheControl.builder()
189                 .setMaxAge(10)
190                 .setSharedMaxAge(20)
191                 .build();
192         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
193         assertEquals(TimeValue.ofSeconds(20), impl.getFreshnessLifetime(cacheControl, entry));
194     }
195 
196     @Test
197     public void testFreshnessLifetimeUsesMaxAgeWhenSharedMaxAgeNotPresent() {
198         // assuming impl represents a shared cache
199         final ResponseCacheControl cacheControl = ResponseCacheControl.builder()
200                 .setMaxAge(10)
201                 .build();
202         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
203         assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(cacheControl, entry));
204     }
205 
206     @Test
207     public void testFreshnessLifetimeIsMaxAgeEvenIfExpiresIsPresent() {
208         final ResponseCacheControl cacheControl = ResponseCacheControl.builder()
209                 .setMaxAge(10)
210                 .build();
211         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
212                 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
213                 new BasicHeader("Expires", DateUtils.formatStandardDate(sixSecondsAgo)));
214         assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(cacheControl, entry));
215     }
216 
217     @Test
218     public void testFreshnessLifetimeIsSMaxAgeEvenIfExpiresIsPresent() {
219         final ResponseCacheControl cacheControl = ResponseCacheControl.builder()
220                 .setSharedMaxAge(10)
221                 .build();
222         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
223                 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
224                 new BasicHeader("Expires", DateUtils.formatStandardDate(sixSecondsAgo)));
225         assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(cacheControl, entry));
226     }
227 
228     @Test
229     public void testFreshnessLifetimeIsFromExpiresHeaderIfNoMaxAge() {
230         final ResponseCacheControl cacheControl = ResponseCacheControl.builder()
231                 .build();
232         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
233                 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
234                 new BasicHeader("Expires", DateUtils.formatStandardDate(sixSecondsAgo)));
235         assertEquals(TimeValue.ofSeconds(4), impl.getFreshnessLifetime(cacheControl, entry));
236     }
237 
238     @Test
239     public void testHeuristicFreshnessLifetime() {
240         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
241                 new BasicHeader("Date", DateUtils.formatStandardDate(oneSecondAgo)),
242                 new BasicHeader("Last-Modified", DateUtils.formatStandardDate(elevenSecondsAgo)));
243         assertEquals(TimeValue.ofSeconds(1), impl.getHeuristicFreshnessLifetime(entry));
244     }
245 
246     @Test
247     public void testHeuristicFreshnessLifetimeDefaultsProperly() {
248         final TimeValue defaultFreshness = TimeValue.ofSeconds(0);
249         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
250         assertEquals(defaultFreshness, impl.getHeuristicFreshnessLifetime(entry));
251     }
252 
253     @Test
254     public void testHeuristicFreshnessLifetimeIsNonNegative() {
255         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
256                 new BasicHeader("Date", DateUtils.formatStandardDate(elevenSecondsAgo)),
257                 new BasicHeader("Last-Modified", DateUtils.formatStandardDate(oneSecondAgo)));
258         assertTrue(TimeValue.isNonNegative(impl.getHeuristicFreshnessLifetime(entry)));
259     }
260 
261     @Test
262     public void testHeuristicFreshnessLifetimeCustomProperly() {
263         final CacheConfig cacheConfig = CacheConfig.custom().setHeuristicDefaultLifetime(TimeValue.ofSeconds(10))
264                 .setHeuristicCoefficient(0.5f).build();
265         impl = new CacheValidityPolicy(cacheConfig);
266         final TimeValue defaultFreshness = TimeValue.ofSeconds(10);
267         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
268         assertEquals(defaultFreshness, impl.getHeuristicFreshnessLifetime(entry));
269     }
270 
271     @Test
272     public void testNegativeAgeHeaderValueReturnsZero() {
273         final Header[] headers = new Header[] { new BasicHeader("Age", "-100") };
274         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
275         // in seconds
276         assertEquals(0, impl.getAgeValue(entry));
277     }
278 
279     @Test
280     public void testMalformedAgeHeaderValueReturnsMaxAge() {
281         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
282                 new BasicHeader("Age", "asdf"));
283         // in seconds
284         assertEquals(0, impl.getAgeValue(entry));
285     }
286 
287     @Test
288     public void testMalformedAgeHeaderMultipleWellFormedAges() {
289         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
290                 new BasicHeader("Age", "123,456,789"));
291         // in seconds
292         assertEquals(123, impl.getAgeValue(entry));
293     }
294 
295     @Test
296     public void testMalformedAgeHeaderMultiplesMalformedAges() {
297         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
298                 new BasicHeader("Age", "123 456 789"));
299         // in seconds
300         assertEquals(0, impl.getAgeValue(entry));
301     }
302 
303     @Test
304     public void testMalformedAgeHeaderNegativeAge() {
305         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
306                 new BasicHeader("Age", "-123"));
307         // in seconds
308         assertEquals(0, impl.getAgeValue(entry));
309     }
310 
311     @Test
312     public void testMalformedAgeHeaderOverflow() {
313         final String reallyOldAge = "1" + Long.MAX_VALUE;
314         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
315                 new BasicHeader("Age", reallyOldAge));
316         // Expect the age value to be 0 in case of overflow
317         assertEquals(0, impl.getAgeValue(entry));
318     }
319 
320 }