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.mockito.ArgumentMatchers.eq;
30 import static org.mockito.Mockito.verify;
31 import static org.mockito.Mockito.when;
32
33 import java.util.Arrays;
34 import java.util.Collection;
35 import java.util.HashMap;
36 import java.util.Map;
37
38 import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
39 import org.apache.hc.client5.http.cache.HttpCacheEntry;
40 import org.apache.hc.client5.http.cache.HttpCacheStorageEntry;
41 import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
42 import org.apache.hc.client5.http.cache.ResourceIOException;
43 import org.hamcrest.CoreMatchers;
44 import org.junit.Assert;
45 import org.junit.Before;
46 import org.junit.Test;
47 import org.mockito.Answers;
48 import org.mockito.ArgumentCaptor;
49 import org.mockito.ArgumentMatchers;
50 import org.mockito.Mockito;
51 import org.mockito.invocation.InvocationOnMock;
52 import org.mockito.stubbing.Answer;
53
54 @SuppressWarnings("boxing")
55 public class TestAbstractSerializingCacheStorage {
56
57 public static byte[] serialize(final String key, final HttpCacheEntry value) throws ResourceIOException {
58 return ByteArrayCacheEntrySerializer.INSTANCE.serialize(new HttpCacheStorageEntry(key, value));
59 }
60
61 private AbstractBinaryCacheStorage<String> impl;
62
63 @Before
64 @SuppressWarnings("unchecked")
65 public void setUp() {
66 impl = Mockito.mock(AbstractBinaryCacheStorage.class,
67 Mockito.withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS).useConstructor(3));
68 }
69
70 @Test
71 public void testCachePut() throws Exception {
72 final String key = "foo";
73 final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
74
75 when(impl.digestToStorageKey(key)).thenReturn("bar");
76
77 impl.putEntry(key, value);
78
79 final ArgumentCaptor<byte[]> argumentCaptor = ArgumentCaptor.forClass(byte[].class);
80 verify(impl).store(eq("bar"), argumentCaptor.capture());
81 Assert.assertArrayEquals(serialize(key, value), argumentCaptor.getValue());
82 }
83
84 @Test
85 public void testCacheGetNullEntry() throws Exception {
86 final String key = "foo";
87
88 when(impl.digestToStorageKey(key)).thenReturn("bar");
89 when(impl.restore("bar")).thenReturn(null);
90
91 final HttpCacheEntry resultingEntry = impl.getEntry(key);
92
93 verify(impl).restore("bar");
94
95 Assert.assertThat(resultingEntry, CoreMatchers.nullValue());
96 }
97
98 @Test
99 public void testCacheGet() throws Exception {
100 final String key = "foo";
101 final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
102
103 when(impl.digestToStorageKey(key)).thenReturn("bar");
104 when(impl.restore("bar")).thenReturn(serialize(key, value));
105
106 final HttpCacheEntry resultingEntry = impl.getEntry(key);
107
108 verify(impl).restore("bar");
109
110 Assert.assertThat(resultingEntry, HttpCacheEntryMatcher.equivalent(value));
111 }
112
113 @Test
114 public void testCacheGetKeyMismatch() throws Exception {
115 final String key = "foo";
116 final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
117
118 when(impl.digestToStorageKey(key)).thenReturn("bar");
119 when(impl.restore("bar")).thenReturn(serialize("not-foo", value));
120
121 final HttpCacheEntry resultingEntry = impl.getEntry(key);
122
123 verify(impl).restore("bar");
124
125 Assert.assertThat(resultingEntry, CoreMatchers.nullValue());
126 }
127
128 @Test
129 public void testCacheRemove() throws Exception{
130 final String key = "foo";
131
132 when(impl.digestToStorageKey(key)).thenReturn("bar");
133 impl.removeEntry(key);
134
135 verify(impl).delete("bar");
136 }
137
138 @Test
139 public void testCacheUpdateNullEntry() throws Exception {
140 final String key = "foo";
141 final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
142
143 when(impl.digestToStorageKey(key)).thenReturn("bar");
144 when(impl.getForUpdateCAS("bar")).thenReturn(null);
145
146 impl.updateEntry(key, new HttpCacheCASOperation() {
147
148 @Override
149 public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
150 Assert.assertThat(existing, CoreMatchers.nullValue());
151 return updatedValue;
152 }
153
154 });
155
156 verify(impl).getForUpdateCAS("bar");
157 verify(impl).store(ArgumentMatchers.eq("bar"), ArgumentMatchers.<byte[]>any());
158 }
159
160 @Test
161 public void testCacheCASUpdate() throws Exception {
162 final String key = "foo";
163 final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
164 final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
165
166 when(impl.digestToStorageKey(key)).thenReturn("bar");
167 when(impl.getForUpdateCAS("bar")).thenReturn("stuff");
168 when(impl.getStorageObject("stuff")).thenReturn(serialize(key, existingValue));
169 when(impl.updateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.eq("stuff"), ArgumentMatchers.<byte[]>any())).thenReturn(true);
170
171 impl.updateEntry(key, new HttpCacheCASOperation() {
172
173 @Override
174 public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
175 return updatedValue;
176 }
177
178 });
179
180 verify(impl).getForUpdateCAS("bar");
181 verify(impl).getStorageObject("stuff");
182 verify(impl).updateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.eq("stuff"), ArgumentMatchers.<byte[]>any());
183 }
184
185 @Test
186 public void testCacheCASUpdateKeyMismatch() throws Exception {
187 final String key = "foo";
188 final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
189 final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
190
191 when(impl.digestToStorageKey(key)).thenReturn("bar");
192 when(impl.getForUpdateCAS("bar")).thenReturn("stuff");
193 when(impl.getStorageObject("stuff")).thenReturn(serialize("not-foo", existingValue));
194 when(impl.updateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.eq("stuff"), ArgumentMatchers.<byte[]>any())).thenReturn(true);
195
196 impl.updateEntry(key, new HttpCacheCASOperation() {
197
198 @Override
199 public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
200 Assert.assertThat(existing, CoreMatchers.nullValue());
201 return updatedValue;
202 }
203
204 });
205
206 verify(impl).getForUpdateCAS("bar");
207 verify(impl).getStorageObject("stuff");
208 verify(impl).store(ArgumentMatchers.eq("bar"), ArgumentMatchers.<byte[]>any());
209 }
210
211 @Test
212 public void testSingleCacheUpdateRetry() throws Exception {
213 final String key = "foo";
214 final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
215 final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
216
217 when(impl.digestToStorageKey(key)).thenReturn("bar");
218 when(impl.getForUpdateCAS("bar")).thenReturn("stuff");
219 when(impl.getStorageObject("stuff")).thenReturn(serialize(key, existingValue));
220 when(impl.updateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.eq("stuff"), ArgumentMatchers.<byte[]>any())).thenReturn(false, true);
221
222 impl.updateEntry(key, new HttpCacheCASOperation() {
223
224 @Override
225 public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
226 return updatedValue;
227 }
228
229 });
230
231 verify(impl, Mockito.times(2)).getForUpdateCAS("bar");
232 verify(impl, Mockito.times(2)).getStorageObject("stuff");
233 verify(impl, Mockito.times(2)).updateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.eq("stuff"), ArgumentMatchers.<byte[]>any());
234 }
235
236 @Test
237 public void testCacheUpdateFail() throws Exception {
238 final String key = "foo";
239 final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
240 final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
241
242 when(impl.digestToStorageKey(key)).thenReturn("bar");
243 when(impl.getForUpdateCAS("bar")).thenReturn("stuff");
244 when(impl.getStorageObject("stuff")).thenReturn(serialize(key, existingValue));
245 when(impl.updateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.eq("stuff"), ArgumentMatchers.<byte[]>any())).thenReturn(false, false, false, true);
246
247 try {
248 impl.updateEntry(key, new HttpCacheCASOperation() {
249
250 @Override
251 public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
252 return updatedValue;
253 }
254
255 });
256 Assert.fail("HttpCacheUpdateException expected");
257 } catch (final HttpCacheUpdateException ignore) {
258 }
259
260 verify(impl, Mockito.times(3)).getForUpdateCAS("bar");
261 verify(impl, Mockito.times(3)).getStorageObject("stuff");
262 verify(impl, Mockito.times(3)).updateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.eq("stuff"), ArgumentMatchers.<byte[]>any());
263 }
264
265 @Test
266 public void testBulkGet() throws Exception {
267 final String key1 = "foo this";
268 final String key2 = "foo that";
269 final String storageKey1 = "bar this";
270 final String storageKey2 = "bar that";
271 final HttpCacheEntry value1 = HttpTestUtils.makeCacheEntry();
272 final HttpCacheEntry value2 = HttpTestUtils.makeCacheEntry();
273
274 when(impl.digestToStorageKey(key1)).thenReturn(storageKey1);
275 when(impl.digestToStorageKey(key2)).thenReturn(storageKey2);
276
277 when(impl.bulkRestore(ArgumentMatchers.<String>anyCollection())).thenAnswer(new Answer<Map<String, byte[]>>() {
278
279 @Override
280 public Map<String, byte[]> answer(final InvocationOnMock invocation) throws Throwable {
281 final Collection<String> keys = invocation.getArgument(0);
282 final Map<String, byte[]> resultMap = new HashMap<>();
283 if (keys.contains(storageKey1)) {
284 resultMap.put(storageKey1, serialize(key1, value1));
285 }
286 if (keys.contains(storageKey2)) {
287 resultMap.put(storageKey2, serialize(key2, value2));
288 }
289 return resultMap;
290 }
291 });
292
293 final Map<String, HttpCacheEntry> entryMap = impl.getEntries(Arrays.asList(key1, key2));
294 Assert.assertThat(entryMap, CoreMatchers.notNullValue());
295 Assert.assertThat(entryMap.get(key1), HttpCacheEntryMatcher.equivalent(value1));
296 Assert.assertThat(entryMap.get(key2), HttpCacheEntryMatcher.equivalent(value2));
297
298 verify(impl, Mockito.times(2)).digestToStorageKey(key1);
299 verify(impl, Mockito.times(2)).digestToStorageKey(key2);
300 verify(impl).bulkRestore(Arrays.asList(storageKey1, storageKey2));
301 }
302
303 @Test
304 public void testBulkGetKeyMismatch() throws Exception {
305 final String key1 = "foo this";
306 final String key2 = "foo that";
307 final String storageKey1 = "bar this";
308 final String storageKey2 = "bar that";
309 final HttpCacheEntry value1 = HttpTestUtils.makeCacheEntry();
310 final HttpCacheEntry value2 = HttpTestUtils.makeCacheEntry();
311
312 when(impl.digestToStorageKey(key1)).thenReturn(storageKey1);
313 when(impl.digestToStorageKey(key2)).thenReturn(storageKey2);
314
315 when(impl.bulkRestore(ArgumentMatchers.<String>anyCollection())).thenAnswer(new Answer<Map<String, byte[]>>() {
316
317 @Override
318 public Map<String, byte[]> answer(final InvocationOnMock invocation) throws Throwable {
319 final Collection<String> keys = invocation.getArgument(0);
320 final Map<String, byte[]> resultMap = new HashMap<>();
321 if (keys.contains(storageKey1)) {
322 resultMap.put(storageKey1, serialize(key1, value1));
323 }
324 if (keys.contains(storageKey2)) {
325 resultMap.put(storageKey2, serialize("not foo", value2));
326 }
327 return resultMap;
328 }
329 });
330
331 final Map<String, HttpCacheEntry> entryMap = impl.getEntries(Arrays.asList(key1, key2));
332 Assert.assertThat(entryMap, CoreMatchers.notNullValue());
333 Assert.assertThat(entryMap.get(key1), HttpCacheEntryMatcher.equivalent(value1));
334 Assert.assertThat(entryMap.get(key2), CoreMatchers.nullValue());
335
336 verify(impl, Mockito.times(2)).digestToStorageKey(key1);
337 verify(impl, Mockito.times(2)).digestToStorageKey(key2);
338 verify(impl).bulkRestore(Arrays.asList(storageKey1, storageKey2));
339 }
340
341 }