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.assertNotSame;
31 import static org.junit.Assert.assertThat;
32 import static org.junit.Assert.fail;
33
34 import java.io.IOException;
35 import java.util.Date;
36 import java.util.HashMap;
37 import java.util.Map;
38
39 import org.apache.hc.client5.http.cache.HttpCacheEntry;
40 import org.apache.hc.client5.http.utils.DateUtils;
41 import org.apache.hc.core5.http.Header;
42 import org.apache.hc.core5.http.HttpResponse;
43 import org.apache.hc.core5.http.HttpStatus;
44 import org.apache.hc.core5.http.message.BasicHeader;
45 import org.apache.hc.core5.http.message.BasicHttpResponse;
46 import org.junit.Before;
47 import org.junit.Test;
48
49 public class TestCacheUpdateHandler {
50
51 private Date requestDate;
52 private Date responseDate;
53
54 private CacheUpdateHandler impl;
55 private HttpCacheEntry entry;
56 private Date now;
57 private Date oneSecondAgo;
58 private Date twoSecondsAgo;
59 private Date eightSecondsAgo;
60 private Date tenSecondsAgo;
61 private HttpResponse response;
62
63 @Before
64 public void setUp() throws Exception {
65 requestDate = new Date(System.currentTimeMillis() - 1000);
66 responseDate = new Date();
67
68 now = new Date();
69 oneSecondAgo = new Date(now.getTime() - 1000L);
70 twoSecondsAgo = new Date(now.getTime() - 2000L);
71 eightSecondsAgo = new Date(now.getTime() - 8000L);
72 tenSecondsAgo = new Date(now.getTime() - 10000L);
73
74 response = new BasicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
75
76 impl = new CacheUpdateHandler();
77 }
78
79 @Test
80 public void testUpdateCacheEntryReturnsDifferentEntryInstance()
81 throws IOException {
82 entry = HttpTestUtils.makeCacheEntry();
83 final HttpCacheEntry newEntry = impl.updateCacheEntry(null, entry,
84 requestDate, responseDate, response);
85 assertNotSame(newEntry, entry);
86 }
87
88 @Test
89 public void testHeadersAreMergedCorrectly() throws IOException {
90 final Header[] headers = {
91 new BasicHeader("Date", DateUtils.formatDate(responseDate)),
92 new BasicHeader("ETag", "\"etag\"")};
93 entry = HttpTestUtils.makeCacheEntry(headers);
94 response.setHeaders();
95
96 final HttpCacheEntry updatedEntry = impl.updateCacheEntry(null, entry,
97 new Date(), new Date(), response);
98
99 assertThat(updatedEntry, ContainsHeaderMatcher.contains("Date", DateUtils.formatDate(responseDate)));
100 assertThat(updatedEntry, ContainsHeaderMatcher.contains("ETag", "\"etag\""));
101 }
102
103 @Test
104 public void testNewerHeadersReplaceExistingHeaders() throws IOException {
105 final Header[] headers = {
106 new BasicHeader("Date", DateUtils.formatDate(requestDate)),
107 new BasicHeader("Cache-Control", "private"),
108 new BasicHeader("ETag", "\"etag\""),
109 new BasicHeader("Last-Modified", DateUtils.formatDate(requestDate)),
110 new BasicHeader("Cache-Control", "max-age=0"),};
111 entry = HttpTestUtils.makeCacheEntry(headers);
112
113 response.setHeaders(new BasicHeader("Last-Modified", DateUtils.formatDate(responseDate)),
114 new BasicHeader("Cache-Control", "public"));
115
116 final HttpCacheEntry updatedEntry = impl.updateCacheEntry(null, entry,
117 new Date(), new Date(), response);
118
119 assertThat(updatedEntry, ContainsHeaderMatcher.contains("Date", DateUtils.formatDate(requestDate)));
120 assertThat(updatedEntry, ContainsHeaderMatcher.contains("ETag", "\"etag\""));
121 assertThat(updatedEntry, ContainsHeaderMatcher.contains("Last-Modified", DateUtils.formatDate(responseDate)));
122 assertThat(updatedEntry, ContainsHeaderMatcher.contains("Cache-Control", "public"));
123 }
124
125 @Test
126 public void testNewHeadersAreAddedByMerge() throws IOException {
127
128 final Header[] headers = {
129 new BasicHeader("Date", DateUtils.formatDate(requestDate)),
130 new BasicHeader("ETag", "\"etag\"")};
131
132 entry = HttpTestUtils.makeCacheEntry(headers);
133 response.setHeaders(new BasicHeader("Last-Modified", DateUtils.formatDate(responseDate)),
134 new BasicHeader("Cache-Control", "public"));
135
136 final HttpCacheEntry updatedEntry = impl.updateCacheEntry(null, entry,
137 new Date(), new Date(), response);
138
139 assertThat(updatedEntry, ContainsHeaderMatcher.contains("Date", DateUtils.formatDate(requestDate)));
140 assertThat(updatedEntry, ContainsHeaderMatcher.contains("ETag", "\"etag\""));
141 assertThat(updatedEntry, ContainsHeaderMatcher.contains("Last-Modified", DateUtils.formatDate(responseDate)));
142 assertThat(updatedEntry, ContainsHeaderMatcher.contains("Cache-Control", "public"));
143 }
144
145 @Test
146 public void oldHeadersRetainedIfResponseOlderThanEntry()
147 throws Exception {
148 final Header[] headers = {
149 new BasicHeader("Date", DateUtils.formatDate(oneSecondAgo)),
150 new BasicHeader("ETag", "\"new-etag\"")
151 };
152 entry = HttpTestUtils.makeCacheEntry(twoSecondsAgo, now, headers);
153 response.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
154 response.setHeader("ETag", "\"old-etag\"");
155 final HttpCacheEntry result = impl.updateCacheEntry("A", entry, new Date(),
156 new Date(), response);
157 assertThat(result, ContainsHeaderMatcher.contains("Date", DateUtils.formatDate(oneSecondAgo)));
158 assertThat(result, ContainsHeaderMatcher.contains("ETag", "\"new-etag\""));
159 }
160
161 @Test
162 public void testUpdatedEntryHasLatestRequestAndResponseDates()
163 throws IOException {
164 entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo);
165 final HttpCacheEntry updated = impl.updateCacheEntry(null, entry,
166 twoSecondsAgo, oneSecondAgo, response);
167
168 assertEquals(twoSecondsAgo, updated.getRequestDate());
169 assertEquals(oneSecondAgo, updated.getResponseDate());
170 }
171
172 @Test
173 public void entry1xxWarningsAreRemovedOnUpdate() throws Exception {
174 final Header[] headers = {
175 new BasicHeader("Warning", "110 fred \"Response is stale\""),
176 new BasicHeader("ETag", "\"old\""),
177 new BasicHeader("Date", DateUtils.formatDate(eightSecondsAgo))
178 };
179 entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo, headers);
180 response.setHeader("ETag", "\"new\"");
181 response.setHeader("Date", DateUtils.formatDate(twoSecondsAgo));
182 final HttpCacheEntry updated = impl.updateCacheEntry(null, entry,
183 twoSecondsAgo, oneSecondAgo, response);
184
185 assertEquals(0, updated.getHeaders("Warning").length);
186 }
187
188 @Test
189 public void entryWithMalformedDateIsStillUpdated() throws Exception {
190 final Header[] headers = {
191 new BasicHeader("ETag", "\"old\""),
192 new BasicHeader("Date", "bad-date")
193 };
194 entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo, headers);
195 response.setHeader("ETag", "\"new\"");
196 response.setHeader("Date", DateUtils.formatDate(twoSecondsAgo));
197 final HttpCacheEntry updated = impl.updateCacheEntry(null, entry,
198 twoSecondsAgo, oneSecondAgo, response);
199
200 assertEquals("\"new\"", updated.getFirstHeader("ETag").getValue());
201 }
202
203 @Test
204 public void entryIsStillUpdatedByResponseWithMalformedDate() throws Exception {
205 final Header[] headers = {
206 new BasicHeader("ETag", "\"old\""),
207 new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo))
208 };
209 entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo, headers);
210 response.setHeader("ETag", "\"new\"");
211 response.setHeader("Date", "bad-date");
212 final HttpCacheEntry updated = impl.updateCacheEntry(null, entry,
213 twoSecondsAgo, oneSecondAgo, response);
214
215 assertEquals("\"new\"", updated.getFirstHeader("ETag").getValue());
216 }
217
218 @Test
219 public void cannotUpdateFromANon304OriginResponse() throws Exception {
220 entry = HttpTestUtils.makeCacheEntry();
221 response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
222 try {
223 impl.updateCacheEntry("A", entry, new Date(), new Date(),
224 response);
225 fail("should have thrown exception");
226 } catch (final IllegalArgumentException expected) {
227 }
228 }
229
230 @Test
231 public void testCacheUpdateAddsVariantURIToParentEntry() throws Exception {
232 final String parentCacheKey = "parentCacheKey";
233 final String variantCacheKey = "variantCacheKey";
234 final String existingVariantKey = "existingVariantKey";
235 final String newVariantCacheKey = "newVariantCacheKey";
236 final String newVariantKey = "newVariantKey";
237 final Map<String,String> existingVariants = new HashMap<>();
238 existingVariants.put(existingVariantKey, variantCacheKey);
239 final HttpCacheEntry parent = HttpTestUtils.makeCacheEntry(existingVariants);
240 final HttpCacheEntry variant = HttpTestUtils.makeCacheEntry();
241
242 final HttpCacheEntry result = impl.updateParentCacheEntry(parentCacheKey, parent, variant, newVariantKey, newVariantCacheKey);
243 final Map<String,String> resultMap = result.getVariantMap();
244 assertEquals(2, resultMap.size());
245 assertEquals(variantCacheKey, resultMap.get(existingVariantKey));
246 assertEquals(newVariantCacheKey, resultMap.get(newVariantKey));
247 }
248
249 @Test
250 public void testContentEncodingHeaderIsNotUpdatedByMerge() throws IOException {
251 final Header[] headers = {
252 new BasicHeader("Date", DateUtils.formatDate(requestDate)),
253 new BasicHeader("ETag", "\"etag\""),
254 new BasicHeader("Content-Encoding", "identity")};
255
256 entry = HttpTestUtils.makeCacheEntry(headers);
257 response.setHeaders(new BasicHeader("Last-Modified", DateUtils.formatDate(responseDate)),
258 new BasicHeader("Cache-Control", "public"),
259 new BasicHeader("Content-Encoding", "gzip"));
260
261 final HttpCacheEntry updatedEntry = impl.updateCacheEntry(null, entry,
262 new Date(), new Date(), response);
263
264 final Header[] updatedHeaders = updatedEntry.getHeaders();
265 headersContain(updatedHeaders, "Content-Encoding", "identity");
266 headersNotContain(updatedHeaders, "Content-Encoding", "gzip");
267 }
268
269 @Test
270 public void testContentLengthIsNotAddedWhenTransferEncodingIsPresent() throws IOException {
271 final Header[] headers = {
272 new BasicHeader("Date", DateUtils.formatDate(requestDate)),
273 new BasicHeader("ETag", "\"etag\""),
274 new BasicHeader("Transfer-Encoding", "chunked")};
275
276 entry = HttpTestUtils.makeCacheEntry(headers);
277 response.setHeaders(new BasicHeader("Last-Modified", DateUtils.formatDate(responseDate)),
278 new BasicHeader("Cache-Control", "public"),
279 new BasicHeader("Content-Length", "0"));
280
281 final HttpCacheEntry updatedEntry = impl.updateCacheEntry(null, entry,
282 new Date(), new Date(), response);
283
284 final Header[] updatedHeaders = updatedEntry.getHeaders();
285 headersContain(updatedHeaders, "Transfer-Encoding", "chunked");
286 headersNotContain(updatedHeaders, "Content-Length", "0");
287 }
288
289 private void headersContain(final Header[] headers, final String name, final String value) {
290 for (final Header header : headers) {
291 if (header.getName().equals(name)) {
292 if (header.getValue().equals(value)) {
293 return;
294 }
295 }
296 }
297 fail("Header [" + name + ": " + value + "] not found in headers.");
298 }
299
300 private void headersNotContain(final Header[] headers, final String name, final String value) {
301 for (final Header header : headers) {
302 if (header.getName().equals(name)) {
303 if (header.getValue().equals(value)) {
304 fail("Header [" + name + ": " + value + "] found in headers where it should not be");
305 }
306 }
307 }
308 }
309 }