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