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.cache;
28  
29  import static org.junit.jupiter.api.Assertions.assertEquals;
30  import static org.junit.jupiter.api.Assertions.assertFalse;
31  import static org.junit.jupiter.api.Assertions.assertNotEquals;
32  import static org.junit.jupiter.api.Assertions.assertNotNull;
33  import static org.junit.jupiter.api.Assertions.assertNull;
34  import static org.junit.jupiter.api.Assertions.assertSame;
35  import static org.junit.jupiter.api.Assertions.assertTrue;
36  import static org.mockito.Mockito.mock;
37  
38  import java.time.Instant;
39  import java.time.LocalDate;
40  import java.time.Month;
41  import java.time.ZoneId;
42  import java.time.temporal.ChronoField;
43  import java.util.Collection;
44  import java.util.HashSet;
45  import java.util.Set;
46  
47  import org.apache.hc.client5.http.impl.cache.HttpTestUtils;
48  import org.apache.hc.client5.http.utils.DateUtils;
49  import org.apache.hc.core5.http.Header;
50  import org.apache.hc.core5.http.HttpHeaders;
51  import org.apache.hc.core5.http.HttpStatus;
52  import org.apache.hc.core5.http.Method;
53  import org.apache.hc.core5.http.message.BasicHeader;
54  import org.apache.hc.core5.http.message.HeaderGroup;
55  import org.junit.jupiter.api.Assertions;
56  import org.junit.jupiter.api.BeforeEach;
57  import org.junit.jupiter.api.Test;
58  
59  public class TestHttpCacheEntry {
60  
61      private Instant now;
62      private Instant elevenSecondsAgo;
63      private Instant nineSecondsAgo;
64      private Resource mockResource;
65      private HttpCacheEntry entry;
66  
67      @BeforeEach
68      public void setUp() {
69          now = Instant.now();
70          elevenSecondsAgo = now.minusSeconds(11);
71          nineSecondsAgo = now.minusSeconds(9);
72          mockResource = mock(Resource.class);
73      }
74  
75      private HttpCacheEntry makeEntry(final Header... headers) {
76          return new HttpCacheEntry(elevenSecondsAgo, nineSecondsAgo,
77                  "GET", "/", HttpTestUtils.headers(),
78                  HttpStatus.SC_OK, HttpTestUtils.headers(headers), mockResource, null);
79      }
80  
81      private HttpCacheEntry makeEntry(final Instant requestDate,
82                                       final Instant responseDate,
83                                       final int status,
84                                       final Header[] headers,
85                                       final Resource resource,
86                                       final Collection<String> variants) {
87          return new HttpCacheEntry(requestDate, responseDate,
88                  "GET", "/", HttpTestUtils.headers(),
89                  status, HttpTestUtils.headers(headers), resource, variants);
90      }
91  
92      private HttpCacheEntry makeEntry(final Instant requestDate,
93                                       final Instant responseDate,
94                                       final int status,
95                                       final Header... headers) {
96          return new HttpCacheEntry(requestDate, responseDate,
97                  "GET", "/", HttpTestUtils.headers(),
98                  status, HttpTestUtils.headers(headers), mockResource, null);
99      }
100 
101     @Test
102     public void testGetHeadersReturnsCorrectHeaders() {
103         entry = makeEntry(
104                 new BasicHeader("bar", "barValue1"),
105                 new BasicHeader("bar", "barValue2"));
106         assertEquals(2, entry.getHeaders("bar").length);
107     }
108 
109     @Test
110     public void testGetFirstHeaderReturnsCorrectHeader() {
111         entry = makeEntry(
112                 new BasicHeader("bar", "barValue1"),
113                 new BasicHeader("bar", "barValue2"));
114         assertEquals("barValue1", entry.getFirstHeader("bar").getValue());
115     }
116 
117     @Test
118     public void testGetHeadersReturnsEmptyArrayIfNoneMatch() {
119         entry = makeEntry(
120                 new BasicHeader("foo", "fooValue"),
121                 new BasicHeader("bar", "barValue1"),
122                 new BasicHeader("bar", "barValue2"));
123         assertEquals(0, entry.getHeaders("baz").length);
124     }
125 
126     @Test
127     public void testGetFirstHeaderReturnsNullIfNoneMatch() {
128         entry = makeEntry(
129                 new BasicHeader("foo", "fooValue"),
130                 new BasicHeader("bar", "barValue1"),
131                 new BasicHeader("bar", "barValue2"));
132         assertNull(entry.getFirstHeader("quux"));
133     }
134 
135     @Test
136     public void testGetMethodReturnsCorrectRequestMethod() {
137         entry = makeEntry(
138                 new BasicHeader("foo", "fooValue"),
139                 new BasicHeader("bar", "barValue1"),
140                 new BasicHeader("bar", "barValue2"));
141         assertEquals(Method.GET.name(), entry.getRequestMethod());
142     }
143 
144     @Test
145     public void statusCodeComesFromOriginalStatusLine() {
146         entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK);
147         assertEquals(HttpStatus.SC_OK, entry.getStatus());
148     }
149 
150     @Test
151     public void canGetOriginalRequestDate() {
152         final Instant requestDate = Instant.now();
153         entry = makeEntry(requestDate, Instant.now(), HttpStatus.SC_OK);
154         assertEquals(requestDate, entry.getRequestInstant());
155     }
156 
157     @Test
158     public void canGetOriginalResponseDate() {
159         final Instant responseDate = Instant.now();
160         entry = makeEntry(Instant.now(), responseDate, HttpStatus.SC_OK);
161         assertEquals(responseDate, entry.getResponseInstant());
162     }
163 
164     @Test
165     public void canGetOriginalResource() {
166         entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK);
167         assertSame(mockResource, entry.getResource());
168     }
169 
170     @Test
171     public void canGetOriginalHeaders() {
172         final Header[] headers = {
173                 new BasicHeader("Server", "MockServer/1.0"),
174                 new BasicHeader("Date", DateUtils.formatStandardDate(now))
175         };
176         entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK, headers, mockResource, null);
177         final Header[] result = entry.getHeaders();
178         assertEquals(headers.length, result.length);
179         for(int i=0; i<headers.length; i++) {
180             assertEquals(headers[i], result[i]);
181         }
182     }
183 
184     @Test
185     public void canRetrieveOriginalVariantMap() {
186         final Set<String> variants = new HashSet<>();
187         variants.add("A");
188         variants.add("B");
189         variants.add("C");
190         entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK,
191                 new Header[]{}, mockResource,
192                 variants);
193         final Set<String> result = entry.getVariants();
194         assertEquals(3, result.size());
195         assertTrue(result.contains("A"));
196         assertTrue(result.contains("B"));
197         assertTrue(result.contains("C"));
198         assertFalse(result.contains("D"));
199     }
200 
201     @Test
202     public void retrievedVariantMapIsNotModifiable() {
203         final Set<String> variants = new HashSet<>();
204         variants.add("A");
205         variants.add("B");
206         variants.add("C");
207         entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK,
208                 new Header[]{}, mockResource,
209                 variants);
210         final Set<String> result = entry.getVariants();
211         Assertions.assertThrows(UnsupportedOperationException.class, () -> result.remove("A"));
212         Assertions.assertThrows(UnsupportedOperationException.class, () -> result.add("D"));
213     }
214 
215     @Test
216     public void canConvertToString() {
217         entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK);
218         assertNotNull(entry.toString());
219         assertNotEquals("", entry.toString());
220     }
221 
222     @Test
223     public void testMissingDateHeaderIsIgnored() {
224         entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK);
225         assertNull(entry.getInstant());
226     }
227 
228     @Test
229     public void testMalformedDateHeaderIsIgnored() {
230         entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK,
231                 new BasicHeader("Date", "asdf"));
232         assertNull(entry.getInstant());
233     }
234 
235     @Test
236     public void testValidDateHeaderIsParsed() {
237         final Instant date = Instant.now().with(ChronoField.MILLI_OF_SECOND, 0);
238         entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK,
239                 new BasicHeader("Date", DateUtils.formatStandardDate(date)));
240         final Instant dateHeaderValue = entry.getInstant();
241         assertNotNull(dateHeaderValue);
242         assertEquals(date, dateHeaderValue);
243     }
244 
245     @Test
246     public void testEpochDateHeaderIsParsed() {
247         entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK,
248                 new BasicHeader("Date", DateUtils.formatStandardDate(Instant.EPOCH)));
249         final Instant dateHeaderValue = entry.getInstant();
250         assertNotNull(dateHeaderValue);
251         assertEquals(Instant.EPOCH, dateHeaderValue);
252     }
253 
254     @Test
255     public void testDateParsedOnce() {
256         final Instant date = Instant.now().with(ChronoField.MILLI_OF_SECOND, 0);
257         entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK,
258                 new BasicHeader("Date", DateUtils.formatStandardDate(date)));
259         final Instant dateHeaderValue = entry.getInstant();
260         assertNotNull(dateHeaderValue);
261         assertSame(dateHeaderValue, entry.getInstant());
262         assertSame(dateHeaderValue, entry.getInstant());
263     }
264 
265     @Test
266     public void testExpiresParsedOnce() {
267         final Instant date = Instant.now().with(ChronoField.MILLI_OF_SECOND, 0);
268         entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK,
269                 new BasicHeader("Last-Modified", DateUtils.formatStandardDate(date)));
270         final Instant lastModifiedHeaderValue = entry.getLastModified();
271         assertNotNull(lastModifiedHeaderValue);
272         assertSame(lastModifiedHeaderValue, entry.getLastModified());
273         assertSame(lastModifiedHeaderValue, entry.getLastModified());
274     }
275 
276     private static Instant createInstant(final int year, final Month month, final int day) {
277         return LocalDate.of(year, month, day).atStartOfDay(ZoneId.of("GMT")).toInstant();
278     }
279 
280     @Test
281     public void testIsCacheEntryNewer() throws Exception {
282         assertFalse(HttpCacheEntry.isNewer(null, null));
283         entry = makeEntry();
284         final HeaderGroup message = new HeaderGroup();
285         assertFalse(HttpCacheEntry.isNewer(entry, message));
286 
287         entry = makeEntry(new BasicHeader(HttpHeaders.DATE, "huh?"));
288         message.setHeader(new BasicHeader(HttpHeaders.DATE, "eh?"));
289         assertFalse(HttpCacheEntry.isNewer(entry, message));
290 
291         entry = makeEntry(new BasicHeader(HttpHeaders.DATE, "huh?"));
292         message.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 26))));
293         assertFalse(HttpCacheEntry.isNewer(entry, message));
294 
295         entry = makeEntry(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 27))));
296         message.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 26))));
297         assertTrue(HttpCacheEntry.isNewer(entry, message));
298 
299         entry = makeEntry(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 25))));
300         message.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 26))));
301         assertFalse(HttpCacheEntry.isNewer(entry, message));
302     }
303 
304 }