/* Copyright (c) 2014-2017 Jakub Białek * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package com.google.code.ssm.aop; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.concurrent.TimeoutException; import org.aspectj.lang.ProceedingJoinPoint; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import com.google.code.ssm.Cache; import com.google.code.ssm.aop.support.AnnotationData; import com.google.code.ssm.aop.support.PertinentNegativeNull; import com.google.code.ssm.api.ParameterValueKeyProvider; import com.google.code.ssm.api.ReadThroughMultiCache; import com.google.code.ssm.api.ReadThroughMultiCacheOption; import com.google.code.ssm.api.format.SerializationType; import com.google.code.ssm.providers.CacheException; /** * * @author Jakub Białek * */ @RunWith(MockitoJUnitRunner.class) public class ReadThroughMultiCacheAdviceCoordTest { private static final String NS = "T1"; private static final int EXPIRATION = 321; private final List<String> expected = Arrays.asList("a", "b"); private final Object[] args = new Object[] { Arrays.asList(1, 2) }; private final List<String> cacheKeys = Arrays.asList(NS + ":" + 1, NS + ":" + 2); @Mock(answer = Answers.RETURNS_DEEP_STUBS) private CacheBase cacheBase; @Mock private ProceedingJoinPoint pjp; @Mock private ReadThroughMultiCache annotation; @Mock private Cache cache; @InjectMocks private ReadThroughMultiCacheAdvice advice = new ReadThroughMultiCacheAdvice(); @Test public void shouldExecuteMethodAndNotModifyArgsIfAllMiss() throws Throwable { final Method methodToCache = TestService.class.getMethod("getList", List.class); initMocks(methodToCache, Collections.<String, Object> emptyMap()); when(pjp.proceed(args)).thenReturn(expected); final Object result = advice.cacheMulti(pjp); assertNotNull(result); assertEquals(expected, result); verify(pjp).proceed(args); verify(cache).getBulk(eq(new HashSet<String>(cacheKeys)), any(SerializationType.class)); for (int i = 0; i < expected.size(); i++) { verify(cache).setSilently(cacheKeys.get(i), EXPIRATION, expected.get(i), null); } } @Test public void shouldExecuteMethodAndModifyArgsIfSomeMiss() throws Throwable { final Method methodToCache = TestService.class.getMethod("getList", List.class); final Object[] modifiedArgs = new Object[] { Arrays.asList(2) }; final Map<String, Object> cacheResponse = Collections.<String, Object> singletonMap(cacheKeys.get(0), expected.get(0)); initMocks(methodToCache, cacheResponse); when(pjp.proceed(modifiedArgs)).thenReturn(Collections.singletonList("b")); final Object result = advice.cacheMulti(pjp); assertNotNull(result); assertEquals(expected, result); verify(pjp).proceed(modifiedArgs); verify(cache).getBulk(eq(new HashSet<String>(cacheKeys)), any(SerializationType.class)); verify(cache).setSilently(NS + ":" + 2, EXPIRATION, "b", null); } @Test public void shouldNotExecuteMethodIfAllHits() throws Throwable { final Method methodToCache = TestService.class.getMethod("getList", List.class); final Map<String, Object> cacheResponse = new HashMap<String, Object>(); cacheResponse.put(cacheKeys.get(0), expected.get(0)); cacheResponse.put(cacheKeys.get(1), expected.get(1)); initMocks(methodToCache, cacheResponse); final Object result = advice.cacheMulti(pjp); assertNotNull(result); assertEquals(expected, result); verify(cache).getBulk(eq(new HashSet<String>(cacheKeys)), any(SerializationType.class)); verify(pjp, never()).proceed(any(Object[].class)); verify(cache, never()).setSilently(anyString(), anyInt(), anyObject(), any(SerializationType.class)); } @Test public void shouldNotAddNullsToCache() throws Throwable { final Method methodToCache = TestService.class.getMethod("getList", List.class); final List<String> expected = Collections.emptyList(); initMocks(methodToCache, Collections.<String, Object> emptyMap()); when(pjp.proceed(args)).thenReturn(expected); final Object result = advice.cacheMulti(pjp); assertNotNull(result); assertEquals(expected, result); verify(pjp).proceed(args); verify(cache).getBulk(eq(new HashSet<String>(cacheKeys)), any(SerializationType.class)); verify(cache, never()).setSilently(anyString(), anyInt(), anyObject(), any(SerializationType.class)); verify(cache, never()).addSilently(anyString(), anyInt(), anyObject(), any(SerializationType.class)); } @Test public void shouldAddNullsToCache() throws Throwable { final Method methodToCache = TestService.class.getMethod("getListCacheNulls", List.class); final List<String> expected = Collections.emptyList(); initMocks(methodToCache, Collections.<String, Object> emptyMap()); when(pjp.proceed(args)).thenReturn(expected); final Object result = advice.cacheMulti(pjp); assertNotNull(result); assertEquals(expected, result); verify(pjp).proceed(args); verify(cache).getBulk(eq(new HashSet<String>(cacheKeys)), any(SerializationType.class)); verify(cache, never()).setSilently(anyString(), anyInt(), anyObject(), any(SerializationType.class)); verify(cache).addSilently(eq(cacheKeys.get(0)), eq(EXPIRATION), eq(PertinentNegativeNull.NULL), any(SerializationType.class)); verify(cache).addSilently(eq(cacheKeys.get(1)), eq(EXPIRATION), eq(PertinentNegativeNull.NULL), any(SerializationType.class)); } private void initMocks(final Method methodToCache, final Map<String, Object> cacheResponse) throws NoSuchMethodException, TimeoutException, CacheException { when(pjp.getArgs()).thenReturn(args); when(cacheBase.getMethodToCache(pjp)).thenReturn(methodToCache); when(cacheBase.getCache(any(AnnotationData.class))).thenReturn(cache); when(cacheBase.getSubmission(anyObject())).thenAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { return (invocation.getArguments()[0] == null) ? PertinentNegativeNull.NULL : invocation.getArguments()[0]; } }); when(cacheBase.getCacheKeyBuilder().getCacheKeys(any(AnnotationData.class), eq(args), eq(methodToCache.toString()))).thenReturn( cacheKeys); when(cache.getBulk(eq(new HashSet<String>(cacheKeys)), any(SerializationType.class))).thenReturn(cacheResponse); } private static class TestService { @ReadThroughMultiCache(namespace = NS, expiration = EXPIRATION) public List<String> getList(@ParameterValueKeyProvider final List<Integer> id1) { return Collections.<String> emptyList(); } @ReadThroughMultiCache(namespace = NS, expiration = EXPIRATION, option = @ReadThroughMultiCacheOption(addNullsToCache = true)) public List<String> getListCacheNulls(@ParameterValueKeyProvider final List<Integer> id1) { return Collections.<String> emptyList(); } } }