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.Mockito.verify;
30 import static org.mockito.Mockito.when;
31
32 import java.util.Arrays;
33 import java.util.Collection;
34 import java.util.HashMap;
35 import java.util.Map;
36 import java.util.concurrent.atomic.AtomicInteger;
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.apache.hc.core5.concurrent.Cancellable;
44 import org.apache.hc.core5.concurrent.FutureCallback;
45 import org.hamcrest.CoreMatchers;
46 import org.junit.Assert;
47 import org.junit.Before;
48 import org.junit.Test;
49 import org.junit.runner.RunWith;
50 import org.mockito.Answers;
51 import org.mockito.ArgumentCaptor;
52 import org.mockito.ArgumentMatchers;
53 import org.mockito.Mock;
54 import org.mockito.Mockito;
55 import org.mockito.invocation.InvocationOnMock;
56 import org.mockito.junit.MockitoJUnitRunner;
57 import org.mockito.stubbing.Answer;
58
59 @RunWith(MockitoJUnitRunner.class)
60 public class TestAbstractSerializingAsyncCacheStorage {
61
62 @Mock
63 private Cancellable cancellable;
64 @Mock
65 private FutureCallback<Boolean> operationCallback;
66 @Mock
67 private FutureCallback<HttpCacheEntry> cacheEntryCallback;
68 @Mock
69 private FutureCallback<Map<String, HttpCacheEntry>> bulkCacheEntryCallback;
70
71 private AbstractBinaryAsyncCacheStorage<String> impl;
72
73 public static byte[] serialize(final String key, final HttpCacheEntry value) throws ResourceIOException {
74 return ByteArrayCacheEntrySerializer.INSTANCE.serialize(new HttpCacheStorageEntry(key, value));
75 }
76
77 @Before
78 @SuppressWarnings("unchecked")
79 public void setUp() {
80 impl = Mockito.mock(AbstractBinaryAsyncCacheStorage.class,
81 Mockito.withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS).useConstructor(3));
82 }
83
84 @Test
85 public void testCachePut() throws Exception {
86 final String key = "foo";
87 final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
88
89 Mockito.when(impl.digestToStorageKey(key)).thenReturn("bar");
90 Mockito.when(impl.store(
91 ArgumentMatchers.eq("bar"),
92 ArgumentMatchers.<byte[]>any(),
93 ArgumentMatchers.<FutureCallback<Boolean>>any())).thenAnswer(new Answer<Cancellable>() {
94
95 @Override
96 public Cancellable answer(final InvocationOnMock invocation) throws Throwable {
97 final FutureCallback<Boolean> callback = invocation.getArgument(2);
98 callback.completed(true);
99 return cancellable;
100 }
101
102 });
103
104 impl.putEntry(key, value, operationCallback);
105
106 final ArgumentCaptor<byte[]> argumentCaptor = ArgumentCaptor.forClass(byte[].class);
107 Mockito.verify(impl).store(ArgumentMatchers.eq("bar"), argumentCaptor.capture(), ArgumentMatchers.<FutureCallback<Boolean>>any());
108 Assert.assertArrayEquals(serialize(key, value), argumentCaptor.getValue());
109 Mockito.verify(operationCallback).completed(Boolean.TRUE);
110 }
111
112 @Test
113 public void testCacheGetNullEntry() throws Exception {
114 final String key = "foo";
115
116 Mockito.when(impl.digestToStorageKey(key)).thenReturn("bar");
117 Mockito.when(impl.restore(ArgumentMatchers.eq("bar"), ArgumentMatchers.<FutureCallback<byte[]>>any())).thenAnswer(new Answer<Cancellable>() {
118
119 @Override
120 public Cancellable answer(final InvocationOnMock invocation) throws Throwable {
121 final FutureCallback<byte[]> callback = invocation.getArgument(1);
122 callback.completed(null);
123 return cancellable;
124 }
125
126 });
127
128 impl.getEntry(key, cacheEntryCallback);
129 final ArgumentCaptor<HttpCacheEntry> argumentCaptor = ArgumentCaptor.forClass(HttpCacheEntry.class);
130 Mockito.verify(cacheEntryCallback).completed(argumentCaptor.capture());
131 Assert.assertThat(argumentCaptor.getValue(), CoreMatchers.nullValue());
132 Mockito.verify(impl).restore(ArgumentMatchers.eq("bar"), ArgumentMatchers.<FutureCallback<byte[]>>any());
133 }
134
135 @Test
136 public void testCacheGet() throws Exception {
137 final String key = "foo";
138 final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
139
140 Mockito.when(impl.digestToStorageKey(key)).thenReturn("bar");
141 Mockito.when(impl.restore(ArgumentMatchers.eq("bar"), ArgumentMatchers.<FutureCallback<byte[]>>any())).thenAnswer(new Answer<Cancellable>() {
142
143 @Override
144 public Cancellable answer(final InvocationOnMock invocation) throws Throwable {
145 final FutureCallback<byte[]> callback = invocation.getArgument(1);
146 callback.completed(serialize(key, value));
147 return cancellable;
148 }
149
150 });
151
152 impl.getEntry(key, cacheEntryCallback);
153 final ArgumentCaptor<HttpCacheEntry> argumentCaptor = ArgumentCaptor.forClass(HttpCacheEntry.class);
154 Mockito.verify(cacheEntryCallback).completed(argumentCaptor.capture());
155 final HttpCacheEntry resultingEntry = argumentCaptor.getValue();
156 Assert.assertThat(resultingEntry, HttpCacheEntryMatcher.equivalent(value));
157 Mockito.verify(impl).restore(ArgumentMatchers.eq("bar"), ArgumentMatchers.<FutureCallback<byte[]>>any());
158 }
159
160 @Test
161 public void testCacheGetKeyMismatch() throws Exception {
162 final String key = "foo";
163 final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
164 Mockito.when(impl.digestToStorageKey(key)).thenReturn("bar");
165 Mockito.when(impl.restore(ArgumentMatchers.eq("bar"), ArgumentMatchers.<FutureCallback<byte[]>>any())).thenAnswer(new Answer<Cancellable>() {
166
167 @Override
168 public Cancellable answer(final InvocationOnMock invocation) throws Throwable {
169 final FutureCallback<byte[]> callback = invocation.getArgument(1);
170 callback.completed(serialize("not-foo", value));
171 return cancellable;
172 }
173
174 });
175
176 impl.getEntry(key, cacheEntryCallback);
177 final ArgumentCaptor<HttpCacheEntry> argumentCaptor = ArgumentCaptor.forClass(HttpCacheEntry.class);
178 Mockito.verify(cacheEntryCallback).completed(argumentCaptor.capture());
179 Assert.assertThat(argumentCaptor.getValue(), CoreMatchers.nullValue());
180 Mockito.verify(impl).restore(ArgumentMatchers.eq("bar"), ArgumentMatchers.<FutureCallback<byte[]>>any());
181 }
182
183 @Test
184 public void testCacheRemove() throws Exception{
185 final String key = "foo";
186
187 Mockito.when(impl.digestToStorageKey(key)).thenReturn("bar");
188 Mockito.when(impl.delete(
189 ArgumentMatchers.eq("bar"),
190 ArgumentMatchers.<FutureCallback<Boolean>>any())).thenAnswer(new Answer<Cancellable>() {
191
192 @Override
193 public Cancellable answer(final InvocationOnMock invocation) throws Throwable {
194 final FutureCallback<Boolean> callback = invocation.getArgument(1);
195 callback.completed(true);
196 return cancellable;
197 }
198
199 });
200 impl.removeEntry(key, operationCallback);
201
202 Mockito.verify(impl).delete("bar", operationCallback);
203 Mockito.verify(operationCallback).completed(Boolean.TRUE);
204 }
205
206 @Test
207 public void testCacheUpdateNullEntry() throws Exception {
208 final String key = "foo";
209 final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
210
211 Mockito.when(impl.digestToStorageKey(key)).thenReturn("bar");
212 Mockito.when(impl.getForUpdateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.<FutureCallback<String>>any())).thenAnswer(new Answer<Cancellable>() {
213
214 @Override
215 public Cancellable answer(final InvocationOnMock invocation) throws Throwable {
216 final FutureCallback<byte[]> callback = invocation.getArgument(1);
217 callback.completed(null);
218 return cancellable;
219 }
220
221 });
222 Mockito.when(impl.store(
223 ArgumentMatchers.eq("bar"),
224 ArgumentMatchers.<byte[]>any(),
225 ArgumentMatchers.<FutureCallback<Boolean>>any())).thenAnswer(new Answer<Cancellable>() {
226
227 @Override
228 public Cancellable answer(final InvocationOnMock invocation) throws Throwable {
229 final FutureCallback<Boolean> callback = invocation.getArgument(2);
230 callback.completed(true);
231 return cancellable;
232 }
233
234 });
235
236 impl.updateEntry(key, new HttpCacheCASOperation() {
237
238 @Override
239 public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
240 Assert.assertThat(existing, CoreMatchers.nullValue());
241 return updatedValue;
242 }
243
244 }, operationCallback);
245
246 Mockito.verify(impl).getForUpdateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.<FutureCallback<String>>any());
247 Mockito.verify(impl).store(ArgumentMatchers.eq("bar"), ArgumentMatchers.<byte[]>any(), ArgumentMatchers.<FutureCallback<Boolean>>any());
248 Mockito.verify(operationCallback).completed(Boolean.TRUE);
249 }
250
251 @Test
252 public void testCacheCASUpdate() throws Exception {
253 final String key = "foo";
254 final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
255 final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
256
257 Mockito.when(impl.digestToStorageKey(key)).thenReturn("bar");
258 Mockito.when(impl.getForUpdateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.<FutureCallback<String>>any())).thenAnswer(new Answer<Cancellable>() {
259
260 @Override
261 public Cancellable answer(final InvocationOnMock invocation) throws Throwable {
262 final FutureCallback<String> callback = invocation.getArgument(1);
263 callback.completed("stuff");
264 return cancellable;
265 }
266
267 });
268 Mockito.when(impl.getStorageObject("stuff")).thenReturn(serialize(key, existingValue));
269 Mockito.when(impl.updateCAS(
270 ArgumentMatchers.eq("bar"),
271 ArgumentMatchers.eq("stuff"),
272 ArgumentMatchers.<byte[]>any(),
273 ArgumentMatchers.<FutureCallback<Boolean>>any())).thenAnswer(new Answer<Cancellable>() {
274
275 @Override
276 public Cancellable answer(final InvocationOnMock invocation) throws Throwable {
277 final FutureCallback<Boolean> callback = invocation.getArgument(3);
278 callback.completed(true);
279 return cancellable;
280 }
281
282 });
283
284 impl.updateEntry(key, new HttpCacheCASOperation() {
285
286 @Override
287 public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
288 return updatedValue;
289 }
290
291 }, operationCallback);
292
293 Mockito.verify(impl).getForUpdateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.<FutureCallback<String>>any());
294 Mockito.verify(impl).getStorageObject("stuff");
295 Mockito.verify(impl).updateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.eq("stuff"), ArgumentMatchers.<byte[]>any(), ArgumentMatchers.<FutureCallback<Boolean>>any());
296 Mockito.verify(operationCallback).completed(Boolean.TRUE);
297 }
298
299 @Test
300 public void testCacheCASUpdateKeyMismatch() throws Exception {
301 final String key = "foo";
302 final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
303 final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
304
305 Mockito.when(impl.digestToStorageKey(key)).thenReturn("bar");
306 Mockito.when(impl.getForUpdateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.<FutureCallback<String>>any())).thenAnswer(
307 new Answer<Cancellable>() {
308
309 @Override
310 public Cancellable answer(final InvocationOnMock invocation) throws Throwable {
311 final FutureCallback<String> callback = invocation.getArgument(1);
312 callback.completed("stuff");
313 return cancellable;
314 }
315
316 });
317 Mockito.when(impl.getStorageObject("stuff")).thenReturn(serialize("not-foo", existingValue));
318 Mockito.when(impl.store(
319 ArgumentMatchers.eq("bar"),
320 ArgumentMatchers.<byte[]>any(),
321 ArgumentMatchers.<FutureCallback<Boolean>>any())).thenAnswer(new Answer<Cancellable>() {
322
323 @Override
324 public Cancellable answer(final InvocationOnMock invocation) throws Throwable {
325 final FutureCallback<Boolean> callback = invocation.getArgument(2);
326 callback.completed(true);
327 return cancellable;
328 }
329
330 });
331
332 impl.updateEntry(key, new HttpCacheCASOperation() {
333
334 @Override
335 public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
336 Assert.assertThat(existing, CoreMatchers.nullValue());
337 return updatedValue;
338 }
339
340 }, operationCallback);
341
342 Mockito.verify(impl).getForUpdateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.<FutureCallback<String>>any());
343 Mockito.verify(impl).getStorageObject("stuff");
344 Mockito.verify(impl, Mockito.never()).updateCAS(
345 ArgumentMatchers.eq("bar"), ArgumentMatchers.eq("stuff"), ArgumentMatchers.<byte[]>any(), ArgumentMatchers.<FutureCallback<Boolean>>any());
346 Mockito.verify(impl).store(ArgumentMatchers.eq("bar"), ArgumentMatchers.<byte[]>any(), ArgumentMatchers.<FutureCallback<Boolean>>any());
347 Mockito.verify(operationCallback).completed(Boolean.TRUE);
348 }
349
350 @Test
351 public void testSingleCacheUpdateRetry() throws Exception {
352 final String key = "foo";
353 final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
354 final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
355
356 Mockito.when(impl.digestToStorageKey(key)).thenReturn("bar");
357 Mockito.when(impl.getForUpdateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.<FutureCallback<String>>any())).thenAnswer(
358 new Answer<Cancellable>() {
359
360 @Override
361 public Cancellable answer(final InvocationOnMock invocation) throws Throwable {
362 final FutureCallback<String> callback = invocation.getArgument(1);
363 callback.completed("stuff");
364 return cancellable;
365 }
366
367 });
368 Mockito.when(impl.getStorageObject("stuff")).thenReturn(serialize(key, existingValue));
369 final AtomicInteger count = new AtomicInteger(0);
370 Mockito.when(impl.updateCAS(
371 ArgumentMatchers.eq("bar"),
372 ArgumentMatchers.eq("stuff"),
373 ArgumentMatchers.<byte[]>any(),
374 ArgumentMatchers.<FutureCallback<Boolean>>any())).thenAnswer(new Answer<Cancellable>() {
375
376 @Override
377 public Cancellable answer(final InvocationOnMock invocation) throws Throwable {
378 final FutureCallback<Boolean> callback = invocation.getArgument(3);
379 if (count.incrementAndGet() == 1) {
380 callback.completed(false);
381 } else {
382 callback.completed(true);
383 }
384 return cancellable;
385 }
386
387 });
388
389 impl.updateEntry(key, new HttpCacheCASOperation() {
390
391 @Override
392 public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
393 return updatedValue;
394 }
395
396 }, operationCallback);
397
398 Mockito.verify(impl, Mockito.times(2)).getForUpdateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.<FutureCallback<String>>any());
399 Mockito.verify(impl, Mockito.times(2)).getStorageObject("stuff");
400 Mockito.verify(impl, Mockito.times(2)).updateCAS(
401 ArgumentMatchers.eq("bar"), ArgumentMatchers.eq("stuff"), ArgumentMatchers.<byte[]>any(), ArgumentMatchers.<FutureCallback<Boolean>>any());
402 Mockito.verify(operationCallback).completed(Boolean.TRUE);
403 }
404
405 @Test
406 public void testCacheUpdateFail() throws Exception {
407 final String key = "foo";
408 final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
409 final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
410
411 Mockito.when(impl.digestToStorageKey(key)).thenReturn("bar");
412 Mockito.when(impl.getForUpdateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.<FutureCallback<String>>any())).thenAnswer(
413 new Answer<Cancellable>() {
414
415 @Override
416 public Cancellable answer(final InvocationOnMock invocation) throws Throwable {
417 final FutureCallback<String> callback = invocation.getArgument(1);
418 callback.completed("stuff");
419 return cancellable;
420 }
421
422 });
423 Mockito.when(impl.getStorageObject("stuff")).thenReturn(serialize(key, existingValue));
424 final AtomicInteger count = new AtomicInteger(0);
425 Mockito.when(impl.updateCAS(
426 ArgumentMatchers.eq("bar"),
427 ArgumentMatchers.eq("stuff"),
428 ArgumentMatchers.<byte[]>any(),
429 ArgumentMatchers.<FutureCallback<Boolean>>any())).thenAnswer(new Answer<Cancellable>() {
430
431 @Override
432 public Cancellable answer(final InvocationOnMock invocation) throws Throwable {
433 final FutureCallback<Boolean> callback = invocation.getArgument(3);
434 if (count.incrementAndGet() <= 3) {
435 callback.completed(false);
436 } else {
437 callback.completed(true);
438 }
439 return cancellable;
440 }
441
442 });
443
444 impl.updateEntry(key, new HttpCacheCASOperation() {
445
446 @Override
447 public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
448 return updatedValue;
449 }
450
451 }, operationCallback);
452
453 Mockito.verify(impl, Mockito.times(3)).getForUpdateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.<FutureCallback<String>>any());
454 Mockito.verify(impl, Mockito.times(3)).getStorageObject("stuff");
455 Mockito.verify(impl, Mockito.times(3)).updateCAS(
456 ArgumentMatchers.eq("bar"), ArgumentMatchers.eq("stuff"), ArgumentMatchers.<byte[]>any(), ArgumentMatchers.<FutureCallback<Boolean>>any());
457 Mockito.verify(operationCallback).failed(ArgumentMatchers.<HttpCacheUpdateException>any());
458 }
459
460 @Test
461 @SuppressWarnings("unchecked")
462 public void testBulkGet() throws Exception {
463 final String key1 = "foo this";
464 final String key2 = "foo that";
465 final String storageKey1 = "bar this";
466 final String storageKey2 = "bar that";
467 final HttpCacheEntry value1 = HttpTestUtils.makeCacheEntry();
468 final HttpCacheEntry value2 = HttpTestUtils.makeCacheEntry();
469
470 when(impl.digestToStorageKey(key1)).thenReturn(storageKey1);
471 when(impl.digestToStorageKey(key2)).thenReturn(storageKey2);
472
473 when(impl.bulkRestore(
474 ArgumentMatchers.<String>anyCollection(),
475 ArgumentMatchers.<FutureCallback<Map<String, byte[]>>>any())).thenAnswer(new Answer<Cancellable>() {
476
477 @Override
478 public Cancellable answer(final InvocationOnMock invocation) throws Throwable {
479 final Collection<String> keys = invocation.getArgument(0);
480 final FutureCallback<Map<String, byte[]>> callback = invocation.getArgument(1);
481 final Map<String, byte[]> resultMap = new HashMap<>();
482 if (keys.contains(storageKey1)) {
483 resultMap.put(storageKey1, serialize(key1, value1));
484 }
485 if (keys.contains(storageKey2)) {
486 resultMap.put(storageKey2, serialize(key2, value2));
487 }
488 callback.completed(resultMap);
489 return cancellable;
490 }
491 });
492
493 impl.getEntries(Arrays.asList(key1, key2), bulkCacheEntryCallback);
494 final ArgumentCaptor<Map<String, HttpCacheEntry>> argumentCaptor = ArgumentCaptor.forClass(Map.class);
495 Mockito.verify(bulkCacheEntryCallback).completed(argumentCaptor.capture());
496
497 final Map<String, HttpCacheEntry> entryMap = argumentCaptor.getValue();
498 Assert.assertThat(entryMap, CoreMatchers.notNullValue());
499 Assert.assertThat(entryMap.get(key1), HttpCacheEntryMatcher.equivalent(value1));
500 Assert.assertThat(entryMap.get(key2), HttpCacheEntryMatcher.equivalent(value2));
501
502 verify(impl, Mockito.times(2)).digestToStorageKey(key1);
503 verify(impl, Mockito.times(2)).digestToStorageKey(key2);
504 verify(impl).bulkRestore(
505 ArgumentMatchers.eq(Arrays.asList(storageKey1, storageKey2)),
506 ArgumentMatchers.<FutureCallback<Map<String, byte[]>>>any());
507 }
508
509 @Test
510 @SuppressWarnings("unchecked")
511 public void testBulkGetKeyMismatch() throws Exception {
512 final String key1 = "foo this";
513 final String key2 = "foo that";
514 final String storageKey1 = "bar this";
515 final String storageKey2 = "bar that";
516 final HttpCacheEntry value1 = HttpTestUtils.makeCacheEntry();
517 final HttpCacheEntry value2 = HttpTestUtils.makeCacheEntry();
518
519 when(impl.digestToStorageKey(key1)).thenReturn(storageKey1);
520 when(impl.digestToStorageKey(key2)).thenReturn(storageKey2);
521
522 when(impl.bulkRestore(
523 ArgumentMatchers.<String>anyCollection(),
524 ArgumentMatchers.<FutureCallback<Map<String, byte[]>>>any())).thenAnswer(new Answer<Cancellable>() {
525
526 @Override
527 public Cancellable answer(final InvocationOnMock invocation) throws Throwable {
528 final Collection<String> keys = invocation.getArgument(0);
529 final FutureCallback<Map<String, byte[]>> callback = invocation.getArgument(1);
530 final Map<String, byte[]> resultMap = new HashMap<>();
531 if (keys.contains(storageKey1)) {
532 resultMap.put(storageKey1, serialize(key1, value1));
533 }
534 if (keys.contains(storageKey2)) {
535 resultMap.put(storageKey2, serialize("not foo", value2));
536 }
537 callback.completed(resultMap);
538 return cancellable;
539 }
540 });
541
542 impl.getEntries(Arrays.asList(key1, key2), bulkCacheEntryCallback);
543 final ArgumentCaptor<Map<String, HttpCacheEntry>> argumentCaptor = ArgumentCaptor.forClass(Map.class);
544 Mockito.verify(bulkCacheEntryCallback).completed(argumentCaptor.capture());
545
546 final Map<String, HttpCacheEntry> entryMap = argumentCaptor.getValue();
547 Assert.assertThat(entryMap, CoreMatchers.notNullValue());
548 Assert.assertThat(entryMap.get(key1), HttpCacheEntryMatcher.equivalent(value1));
549 Assert.assertThat(entryMap.get(key2), CoreMatchers.nullValue());
550
551 verify(impl, Mockito.times(2)).digestToStorageKey(key1);
552 verify(impl, Mockito.times(2)).digestToStorageKey(key2);
553 verify(impl).bulkRestore(
554 ArgumentMatchers.eq(Arrays.asList(storageKey1, storageKey2)),
555 ArgumentMatchers.<FutureCallback<Map<String, byte[]>>>any());
556 }
557
558 }