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.http.impl.client.cache.memcached;
28  
29  import static org.mockito.Mockito.doThrow;
30  import static org.mockito.Mockito.mock;
31  import static org.mockito.Mockito.times;
32  import static org.mockito.Mockito.verify;
33  import static org.mockito.Mockito.verifyNoMoreInteractions;
34  import static org.mockito.Mockito.when;
35  
36  import java.io.IOException;
37  import java.io.UnsupportedEncodingException;
38  
39  import junit.framework.TestCase;
40  import net.spy.memcached.CASResponse;
41  import net.spy.memcached.CASValue;
42  import net.spy.memcached.MemcachedClientIF;
43  import net.spy.memcached.OperationTimeoutException;
44  
45  import org.apache.http.client.cache.HttpCacheEntry;
46  import org.apache.http.client.cache.HttpCacheUpdateCallback;
47  import org.apache.http.client.cache.HttpCacheUpdateException;
48  import org.apache.http.impl.client.cache.CacheConfig;
49  import org.apache.http.impl.client.cache.HttpTestUtils;
50  import org.junit.Before;
51  import org.junit.Test;
52  
53  public class TestMemcachedHttpCacheStorage extends TestCase {
54      private MemcachedHttpCacheStorage impl;
55      private MemcachedClientIF mockMemcachedClient;
56      private KeyHashingScheme mockKeyHashingScheme;
57      private MemcachedCacheEntryFactory mockMemcachedCacheEntryFactory;
58      private MemcachedCacheEntry mockMemcachedCacheEntry;
59      private MemcachedCacheEntry mockMemcachedCacheEntry2;
60      private MemcachedCacheEntry mockMemcachedCacheEntry3;
61      private MemcachedCacheEntry mockMemcachedCacheEntry4;
62  
63      @Override
64      @Before
65      public void setUp() throws Exception {
66          mockMemcachedClient = mock(MemcachedClientIF.class);
67          mockKeyHashingScheme = mock(KeyHashingScheme.class);
68          mockMemcachedCacheEntryFactory = mock(MemcachedCacheEntryFactory.class);
69          mockMemcachedCacheEntry = mock(MemcachedCacheEntry.class);
70          mockMemcachedCacheEntry2 = mock(MemcachedCacheEntry.class);
71          mockMemcachedCacheEntry3 = mock(MemcachedCacheEntry.class);
72          mockMemcachedCacheEntry4 = mock(MemcachedCacheEntry.class);
73          final CacheConfig config = CacheConfig.custom().setMaxUpdateRetries(1).build();
74          impl = new MemcachedHttpCacheStorage(mockMemcachedClient, config,
75                  mockMemcachedCacheEntryFactory, mockKeyHashingScheme);
76      }
77  
78      @Test
79      public void testSuccessfulCachePut() throws IOException {
80          final String url = "foo";
81          final String key = "key";
82          final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
83          final byte[] serialized = HttpTestUtils.getRandomBytes(128);
84  
85          when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, value))
86              .thenReturn(mockMemcachedCacheEntry);
87          when(mockMemcachedCacheEntry.toByteArray())
88              .thenReturn(serialized);
89          when(mockKeyHashingScheme.hash(url))
90              .thenReturn(key);
91          when(mockMemcachedClient.set(key, 0, serialized))
92              .thenReturn(null);
93  
94          impl.putEntry(url, value);
95          verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, value);
96          verify(mockMemcachedCacheEntry).toByteArray();
97          verify(mockKeyHashingScheme).hash(url);
98          verify(mockMemcachedClient).set(key, 0, serialized);
99      }
100 
101     @Test
102     public void testCachePutFailsSilentlyWhenWeCannotHashAKey() throws IOException {
103         final String url = "foo";
104         final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
105         final byte[] serialized = HttpTestUtils.getRandomBytes(128);
106 
107         when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, value))
108             .thenReturn(mockMemcachedCacheEntry);
109         when(mockMemcachedCacheEntry.toByteArray())
110             .thenReturn(serialized);
111         when(mockKeyHashingScheme.hash(url))
112             .thenThrow(new MemcachedKeyHashingException(new Exception()));
113 
114         impl.putEntry(url, value);
115 
116         verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, value);
117         verify(mockMemcachedCacheEntry).toByteArray();
118         verify(mockKeyHashingScheme).hash(url);
119     }
120 
121     public void testThrowsIOExceptionWhenMemcachedPutTimesOut() {
122         final String url = "foo";
123         final String key = "key";
124         final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
125         final byte[] serialized = HttpTestUtils.getRandomBytes(128);
126 
127         when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, value))
128             .thenReturn(mockMemcachedCacheEntry);
129         when(mockMemcachedCacheEntry.toByteArray())
130             .thenReturn(serialized);
131         when(mockKeyHashingScheme.hash(url))
132             .thenReturn(key);
133         when(mockMemcachedClient.set(key, 0, serialized))
134             .thenThrow(new OperationTimeoutException("timed out"));
135 
136         try {
137             impl.putEntry(url, value);
138             fail("should have thrown exception");
139         } catch (final IOException expected) {
140         }
141 
142         verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, value);
143         verify(mockMemcachedCacheEntry).toByteArray();
144         verify(mockKeyHashingScheme).hash(url);
145         verify(mockMemcachedClient).set(key, 0, serialized);
146     }
147 
148     @Test
149     public void testCachePutThrowsIOExceptionIfCannotSerializeEntry() {
150         final String url = "foo";
151         final String key = "key";
152         final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
153 
154         when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, value))
155             .thenReturn(mockMemcachedCacheEntry);
156         when(mockMemcachedCacheEntry.toByteArray())
157             .thenThrow(new MemcachedSerializationException(new Exception()));
158 
159         try {
160             impl.putEntry(url, value);
161             fail("should have thrown exception");
162         } catch (final IOException expected) {
163 
164         }
165 
166         verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, value);
167         verify(mockMemcachedCacheEntry).toByteArray();
168     }
169 
170     @Test
171     public void testSuccessfulCacheGet() throws UnsupportedEncodingException,
172             IOException {
173         final String url = "foo";
174         final String key = "key";
175         final byte[] serialized = HttpTestUtils.getRandomBytes(128);
176         final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry();
177 
178         when(mockKeyHashingScheme.hash(url)).thenReturn(key);
179         when(mockMemcachedClient.get(key)).thenReturn(serialized);
180         when(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
181             .thenReturn(mockMemcachedCacheEntry);
182         when(mockMemcachedCacheEntry.getStorageKey()).thenReturn(url);
183         when(mockMemcachedCacheEntry.getHttpCacheEntry()).thenReturn(cacheEntry);
184 
185         final HttpCacheEntry resultingEntry = impl.getEntry(url);
186 
187         verify(mockKeyHashingScheme).hash(url);
188         verify(mockMemcachedClient).get(key);
189         verify(mockMemcachedCacheEntryFactory).getUnsetCacheEntry();
190         verify(mockMemcachedCacheEntry).set(serialized);
191         verify(mockMemcachedCacheEntry).getStorageKey();
192         verify(mockMemcachedCacheEntry).getHttpCacheEntry();
193 
194         assertSame(cacheEntry, resultingEntry);
195     }
196 
197     @Test
198     public void testTreatsNoneByteArrayFromMemcachedAsCacheMiss() throws UnsupportedEncodingException,
199             IOException {
200         final String url = "foo";
201         final String key = "key";
202 
203         when(mockKeyHashingScheme.hash(url)).thenReturn(key);
204         when(mockMemcachedClient.get(key)).thenReturn(new Object());
205 
206         final HttpCacheEntry resultingEntry = impl.getEntry(url);
207 
208         verify(mockKeyHashingScheme).hash(url);
209         verify(mockMemcachedClient).get(key);
210 
211         assertNull(resultingEntry);
212     }
213 
214     @Test
215     public void testTreatsNullFromMemcachedAsCacheMiss() throws UnsupportedEncodingException,
216             IOException {
217         final String url = "foo";
218         final String key = "key";
219 
220         when(mockKeyHashingScheme.hash(url)).thenReturn(key);
221         when(mockMemcachedClient.get(key)).thenReturn(null);
222 
223         final HttpCacheEntry resultingEntry = impl.getEntry(url);
224 
225         verify(mockKeyHashingScheme).hash(url);
226         verify(mockMemcachedClient).get(key);
227 
228         assertNull(resultingEntry);
229     }
230 
231     @Test
232     public void testTreatsAsCacheMissIfCannotReconstituteEntry() throws UnsupportedEncodingException,
233             IOException {
234         final String url = "foo";
235         final String key = "key";
236         final byte[] serialized = HttpTestUtils.getRandomBytes(128);
237 
238         when(mockKeyHashingScheme.hash(url)).thenReturn(key);
239         when(mockMemcachedClient.get(key)).thenReturn(serialized);
240         when(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
241             .thenReturn(mockMemcachedCacheEntry);
242         doThrow(new MemcachedSerializationException(new Exception())).when(mockMemcachedCacheEntry).set(serialized);
243 
244         assertNull(impl.getEntry(url));
245 
246         verify(mockKeyHashingScheme).hash(url);
247         verify(mockMemcachedClient).get(key);
248         verify(mockMemcachedCacheEntryFactory).getUnsetCacheEntry();
249         verify(mockMemcachedCacheEntry).set(serialized);
250     }
251 
252     @Test
253     public void testTreatsAsCacheMissIfCantHashStorageKey() throws UnsupportedEncodingException,
254             IOException {
255         final String url = "foo";
256 
257         when(mockKeyHashingScheme.hash(url)).thenThrow(new MemcachedKeyHashingException(new Exception()));
258 
259         assertNull(impl.getEntry(url));
260         verify(mockKeyHashingScheme).hash(url);
261     }
262 
263     @Test
264     public void testThrowsIOExceptionIfMemcachedTimesOutOnGet() {
265         final String url = "foo";
266         final String key = "key";
267         when(mockKeyHashingScheme.hash(url)).thenReturn(key);
268         when(mockMemcachedClient.get(key))
269             .thenThrow(new OperationTimeoutException(""));
270 
271         try {
272             impl.getEntry(url);
273             fail("should have thrown exception");
274         } catch (final IOException expected) {
275         }
276         verify(mockKeyHashingScheme).hash(url);
277         verify(mockMemcachedClient).get(key);
278     }
279 
280     @Test
281     public void testCacheRemove() throws IOException {
282         final String url = "foo";
283         final String key = "key";
284         when(mockKeyHashingScheme.hash(url)).thenReturn(key);
285         when(mockMemcachedClient.delete(key)).thenReturn(null);
286 
287         impl.removeEntry(url);
288 
289         verify(mockKeyHashingScheme).hash(url);
290         verify(mockMemcachedClient).delete(key);
291     }
292 
293     @Test
294     public void testCacheRemoveHandlesKeyHashingFailure() throws IOException {
295         final String url = "foo";
296         when(mockKeyHashingScheme.hash(url)).thenReturn(null);
297         impl.removeEntry(url);
298         verify(mockKeyHashingScheme).hash(url);
299     }
300 
301     @Test
302     public void testCacheRemoveThrowsIOExceptionOnMemcachedTimeout() {
303         final String url = "foo";
304         final String key = "key";
305         when(mockKeyHashingScheme.hash(url)).thenReturn(key);
306         when(mockMemcachedClient.delete(key))
307             .thenThrow(new OperationTimeoutException(""));
308 
309         try {
310             impl.removeEntry(url);
311             fail("should have thrown exception");
312         } catch (final IOException expected) {
313         }
314 
315         verify(mockKeyHashingScheme).hash(url);
316         verify(mockMemcachedClient).delete(key);
317     }
318 
319     @Test
320     public void testCacheUpdateCanUpdateNullEntry() throws IOException,
321             HttpCacheUpdateException {
322         final String url = "foo";
323         final String key = "key";
324         final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
325         final byte[] serialized = HttpTestUtils.getRandomBytes(128);
326 
327         final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
328             @Override
329             public HttpCacheEntry update(final HttpCacheEntry old) {
330                 assertNull(old);
331                 return updatedValue;
332             }
333         };
334 
335         // get empty old entry
336         when(mockKeyHashingScheme.hash(url)).thenReturn(key);
337         when(mockMemcachedClient.gets(key)).thenReturn(null);
338         when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue))
339             .thenReturn(mockMemcachedCacheEntry);
340         when(mockMemcachedCacheEntry.toByteArray()).thenReturn(serialized);
341         when(
342                 mockMemcachedClient.set(key, 0,
343                         serialized)).thenReturn(null);
344 
345         impl.updateEntry(url, callback);
346 
347         verify(mockKeyHashingScheme, times(2)).hash(url);
348         verify(mockMemcachedClient).gets(key);
349         verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, updatedValue);
350         verify(mockMemcachedCacheEntry).toByteArray();
351         verify(mockMemcachedClient).set(key,  0, serialized);
352     }
353 
354     @Test
355     public void testCacheUpdateOverwritesNonMatchingHashCollision() throws IOException,
356             HttpCacheUpdateException {
357         final String url = "foo";
358         final String key = "key";
359         final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
360         final byte[] oldBytes = HttpTestUtils.getRandomBytes(128);
361         final CASValue<Object> casValue = new CASValue<Object>(-1, oldBytes);
362         final byte[] newBytes = HttpTestUtils.getRandomBytes(128);
363 
364         final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
365             @Override
366             public HttpCacheEntry update(final HttpCacheEntry old) {
367                 assertNull(old);
368                 return updatedValue;
369             }
370         };
371 
372         // get empty old entry
373         when(mockKeyHashingScheme.hash(url)).thenReturn(key);
374         when(mockMemcachedClient.gets(key)).thenReturn(casValue);
375         when(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
376             .thenReturn(mockMemcachedCacheEntry);
377         when(mockMemcachedCacheEntry.getStorageKey()).thenReturn("not" + url);
378 
379         when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue))
380             .thenReturn(mockMemcachedCacheEntry2);
381         when(mockMemcachedCacheEntry2.toByteArray()).thenReturn(newBytes);
382         when(
383                 mockMemcachedClient.set(key, 0,
384                         newBytes)).thenReturn(null);
385 
386         impl.updateEntry(url, callback);
387 
388         verify(mockKeyHashingScheme, times(2)).hash(url);
389         verify(mockMemcachedClient).gets(key);
390         verify(mockMemcachedCacheEntryFactory).getUnsetCacheEntry();
391         verify(mockMemcachedCacheEntry).getStorageKey();
392         verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, updatedValue);
393         verify(mockMemcachedCacheEntry2).toByteArray();
394         verify(mockMemcachedClient).set(key,  0, newBytes);
395     }
396 
397     @Test
398     public void testCacheUpdateCanUpdateExistingEntry() throws IOException,
399             HttpCacheUpdateException {
400         final String url = "foo";
401         final String key = "key";
402         final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
403         final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
404         final byte[] oldBytes = HttpTestUtils.getRandomBytes(128);
405         final CASValue<Object> casValue = new CASValue<Object>(1, oldBytes);
406         final byte[] newBytes = HttpTestUtils.getRandomBytes(128);
407 
408 
409         final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
410             @Override
411             public HttpCacheEntry update(final HttpCacheEntry old) {
412                 assertSame(existingValue, old);
413                 return updatedValue;
414             }
415         };
416 
417         // get empty old entry
418         when(mockKeyHashingScheme.hash(url)).thenReturn(key);
419         when(mockMemcachedClient.gets(key)).thenReturn(casValue);
420         when(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
421             .thenReturn(mockMemcachedCacheEntry);
422         when(mockMemcachedCacheEntry.getStorageKey()).thenReturn(url);
423         when(mockMemcachedCacheEntry.getHttpCacheEntry()).thenReturn(existingValue);
424 
425         when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue))
426             .thenReturn(mockMemcachedCacheEntry2);
427         when(mockMemcachedCacheEntry2.toByteArray()).thenReturn(newBytes);
428 
429         when(
430                 mockMemcachedClient.cas(key, casValue.getCas(),
431                         newBytes)).thenReturn(CASResponse.OK);
432 
433         impl.updateEntry(url, callback);
434 
435         verify(mockKeyHashingScheme).hash(url);
436         verify(mockMemcachedClient).gets(key);
437         verify(mockMemcachedCacheEntryFactory).getUnsetCacheEntry();
438         verify(mockMemcachedCacheEntry).getStorageKey();
439         verify(mockMemcachedCacheEntry).getHttpCacheEntry();
440         verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, updatedValue);
441         verify(mockMemcachedCacheEntry2).toByteArray();
442         verify(mockMemcachedClient).cas(key, casValue.getCas(), newBytes);
443     }
444 
445     @Test
446     public void testCacheUpdateThrowsExceptionsIfCASFailsEnoughTimes() throws IOException {
447         final String url = "foo";
448         final String key = "key";
449         final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
450         final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
451         final byte[] oldBytes = HttpTestUtils.getRandomBytes(128);
452         final CASValue<Object> casValue = new CASValue<Object>(1, oldBytes);
453         final byte[] newBytes = HttpTestUtils.getRandomBytes(128);
454 
455         final CacheConfig config = CacheConfig.custom().setMaxUpdateRetries(0).build();
456         impl = new MemcachedHttpCacheStorage(mockMemcachedClient, config,
457                 mockMemcachedCacheEntryFactory, mockKeyHashingScheme);
458 
459         final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
460             @Override
461             public HttpCacheEntry update(final HttpCacheEntry old) {
462                 assertSame(existingValue, old);
463                 return updatedValue;
464             }
465         };
466 
467         // get empty old entry
468         when(mockKeyHashingScheme.hash(url)).thenReturn(key);
469         when(mockMemcachedClient.gets(key)).thenReturn(casValue);
470         when(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
471             .thenReturn(mockMemcachedCacheEntry);
472         when(mockMemcachedCacheEntry.getStorageKey()).thenReturn(url);
473         when(mockMemcachedCacheEntry.getHttpCacheEntry()).thenReturn(existingValue);
474 
475         when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue))
476             .thenReturn(mockMemcachedCacheEntry2);
477         when(mockMemcachedCacheEntry2.toByteArray()).thenReturn(newBytes);
478 
479         when(
480                 mockMemcachedClient.cas(key, casValue.getCas(),
481                         newBytes)).thenReturn(CASResponse.EXISTS);
482 
483         try {
484             impl.updateEntry(url, callback);
485             fail("should have thrown exception");
486         } catch (final HttpCacheUpdateException expected) {
487         }
488 
489         verify(mockKeyHashingScheme).hash(url);
490         verify(mockMemcachedClient).gets(key);
491         verify(mockMemcachedCacheEntryFactory).getUnsetCacheEntry();
492         verify(mockMemcachedCacheEntry).getStorageKey();
493         verify(mockMemcachedCacheEntry).getHttpCacheEntry();
494         verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, updatedValue);
495         verify(mockMemcachedCacheEntry2).toByteArray();
496         verify(mockMemcachedClient).cas(key, casValue.getCas(), newBytes);
497     }
498 
499 
500     @Test
501     public void testCacheUpdateCanUpdateExistingEntryWithRetry() throws IOException,
502             HttpCacheUpdateException {
503         final String url = "foo";
504         final String key = "key";
505         final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
506         final HttpCacheEntry existingValue2 = HttpTestUtils.makeCacheEntry();
507         final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
508         final HttpCacheEntry updatedValue2 = HttpTestUtils.makeCacheEntry();
509         final byte[] oldBytes2 = HttpTestUtils.getRandomBytes(128);
510         final CASValue<Object> casValue2 = new CASValue<Object>(2, oldBytes2);
511         final byte[] newBytes2 = HttpTestUtils.getRandomBytes(128);
512 
513         final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
514             @Override
515             public HttpCacheEntry update(final HttpCacheEntry old) {
516                 if (old == existingValue) {
517                     return updatedValue;
518                 }
519                 assertSame(existingValue2, old);
520                 return updatedValue2;
521             }
522         };
523 
524         when(mockKeyHashingScheme.hash(url)).thenReturn(key);
525 
526         // take two
527         when(mockMemcachedClient.gets(key)).thenReturn(casValue2);
528         when(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
529             .thenReturn(mockMemcachedCacheEntry3);
530         when(mockMemcachedCacheEntry3.getStorageKey()).thenReturn(url);
531         when(mockMemcachedCacheEntry3.getHttpCacheEntry()).thenReturn(existingValue2);
532 
533         when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue2))
534             .thenReturn(mockMemcachedCacheEntry4);
535         when(mockMemcachedCacheEntry4.toByteArray()).thenReturn(newBytes2);
536 
537         when(
538                 mockMemcachedClient.cas(key, casValue2.getCas(),
539                         newBytes2)).thenReturn(CASResponse.OK);
540 
541         impl.updateEntry(url, callback);
542 
543         verify(mockKeyHashingScheme).hash(url);
544         verify(mockMemcachedClient).gets(key);
545         verify(mockMemcachedCacheEntryFactory).getUnsetCacheEntry();
546 
547         verify(mockMemcachedCacheEntry3).set(oldBytes2);
548         verify(mockMemcachedCacheEntry3).getStorageKey();
549         verify(mockMemcachedCacheEntry3).getHttpCacheEntry();
550         verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, updatedValue2);
551         verify(mockMemcachedCacheEntry4).toByteArray();
552         verify(mockMemcachedClient).cas(key, casValue2.getCas(), newBytes2);
553 
554         verifyNoMoreInteractions(mockMemcachedClient);
555         verifyNoMoreInteractions(mockKeyHashingScheme);
556         verifyNoMoreInteractions(mockMemcachedCacheEntry);
557         verifyNoMoreInteractions(mockMemcachedCacheEntry2);
558         verifyNoMoreInteractions(mockMemcachedCacheEntry3);
559         verifyNoMoreInteractions(mockMemcachedCacheEntry4);
560         verifyNoMoreInteractions(mockMemcachedCacheEntryFactory);
561     }
562 
563 
564     @Test
565     public void testUpdateThrowsIOExceptionIfMemcachedTimesOut() throws HttpCacheUpdateException {
566         final String url = "foo";
567         final String key = "key";
568         final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
569 
570         final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
571             @Override
572             public HttpCacheEntry update(final HttpCacheEntry old) {
573                 assertNull(old);
574                 return updatedValue;
575             }
576         };
577 
578         // get empty old entry
579         when(mockKeyHashingScheme.hash(url)).thenReturn(key);
580         when(mockMemcachedClient.gets(key))
581             .thenThrow(new OperationTimeoutException(""));
582 
583         try {
584             impl.updateEntry(url, callback);
585             fail("should have thrown exception");
586         } catch (final IOException expected) {
587         }
588 
589         verify(mockKeyHashingScheme).hash(url);
590         verify(mockMemcachedClient).gets(key);
591     }
592 
593 
594     @Test(expected=HttpCacheUpdateException.class)
595     public void testThrowsExceptionOnUpdateIfCannotHashStorageKey() throws Exception {
596         final String url = "foo";
597 
598         when(mockKeyHashingScheme.hash(url))
599             .thenThrow(new MemcachedKeyHashingException(new Exception()));
600 
601         try {
602             impl.updateEntry(url, null);
603             fail("should have thrown exception");
604         } catch (final HttpCacheUpdateException expected) {
605         }
606 
607         verify(mockKeyHashingScheme).hash(url);
608     }
609 }