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