package kr.pe.kwonnam.hibernate4memcached.spymemcached; import kr.pe.kwonnam.hibernate4memcached.memcached.CacheNamespace; import kr.pe.kwonnam.hibernate4memcached.util.OverridableReadOnlyProperties; import kr.pe.kwonnam.hibernate4memcached.util.OverridableReadOnlyPropertiesImpl; import net.spy.memcached.*; import net.spy.memcached.auth.AuthDescriptor; import net.spy.memcached.transcoders.Transcoder; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import java.util.Properties; import static kr.pe.kwonnam.hibernate4memcached.spymemcached.SpyMemcachedAdapter.*; import static org.fest.assertions.api.Assertions.assertThat; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) public class SpyMemcachedAdapterTest { @Rule public ExpectedException expectedException = ExpectedException.none(); @Mock private MemcachedClientIF memcachedClient; @Captor private ArgumentCaptor<Long> longCaptor; private SpyMemcachedAdapter spyMemcachedAdapter; private long startTimeMills; private CacheNamespace givenCacheNamespace; private String givenKey; private String givenNamespacedKey; @Before public void setUp() throws Exception { spyMemcachedAdapter = new SpyMemcachedAdapter(); spyMemcachedAdapter.setMemcachedClient(memcachedClient); spyMemcachedAdapter.setCacheKeyPrefix("hXm"); spyMemcachedAdapter = spy(spyMemcachedAdapter); startTimeMills = System.currentTimeMillis(); } @Test public void createConnectionFactoryBuilder() throws Exception { Properties props = new Properties(); props.setProperty(HASH_ALGORITHM_PROPERTY_KEY, DefaultHashAlgorithm.NATIVE_HASH.name()); props.setProperty(OPERATION_TIMEOUT_MILLIS_PROPERTY_KEY, String.valueOf(13579)); props.setProperty(TRANSCODER_PROPERTY_KEY, FakeTranscoder.class.getName()); props.setProperty(AUTH_GENERATOR_PROPERTY_KEY, FakeAuthDescriptorGenerator.class.getName()); ConnectionFactoryBuilder builder = spyMemcachedAdapter.createConnectionFactoryBuilder(new OverridableReadOnlyPropertiesImpl(props)); ConnectionFactory connectionFactory = builder.build(); assertThat(connectionFactory.getHashAlg()).isEqualTo(DefaultHashAlgorithm.NATIVE_HASH); assertThat(connectionFactory.getOperationTimeout()).isEqualTo(13579); Transcoder<Object> transcoder = connectionFactory.getDefaultTranscoder(); assertThat(transcoder).isExactlyInstanceOf(FakeTranscoder.class); FakeTranscoder fakeTranscoder = (FakeTranscoder) transcoder; assertThat(fakeTranscoder.isInitialized()).isTrue(); AuthDescriptor authDescriptor = connectionFactory.getAuthDescriptor(); assertThat(authDescriptor.getMechs()).isEqualTo(FakeAuthDescriptorGenerator.FAKE_MECHS); } @Test public void authenticate_no_authentication_property() throws Exception { Properties props = new Properties(); ConnectionFactoryBuilder builder = new ConnectionFactoryBuilder(); spyMemcachedAdapter.authenticate(builder, new OverridableReadOnlyPropertiesImpl(props)); assertThat(builder.build().getAuthDescriptor()).isNull(); } @Test public void authenticate_with_authentication_proerty() throws Exception { Properties props = new Properties(); props.setProperty(SpyMemcachedAdapter.AUTH_GENERATOR_PROPERTY_KEY, FakeAuthDescriptorGenerator.class.getName()); ConnectionFactoryBuilder builder = new ConnectionFactoryBuilder(); spyMemcachedAdapter.authenticate(builder, new OverridableReadOnlyPropertiesImpl(props)); ConnectionFactory connectionFactory = builder.build(); assertThat(connectionFactory.getAuthDescriptor().getMechs()).isEqualTo(FakeAuthDescriptorGenerator.FAKE_MECHS); assertThat(connectionFactory.getAuthWaitTime()).isEqualTo(SpyMemcachedAdapter.DEFAULT_AUTH_WAIT_TIME_MILLIS); } @Test public void authenticate_with_authWaitTimeMillis_property() throws Exception { Properties props = new Properties(); props.setProperty(SpyMemcachedAdapter.AUTH_GENERATOR_PROPERTY_KEY, FakeAuthDescriptorGenerator.class.getName()); props.setProperty(SpyMemcachedAdapter.AUTH_WAIT_TIME_MILLIS_PROPERTY_KEY, String.valueOf(9999L)); ConnectionFactoryBuilder builder = new ConnectionFactoryBuilder(); spyMemcachedAdapter.authenticate(builder, new OverridableReadOnlyPropertiesImpl(props)); ConnectionFactory connectionFactory = builder.build(); assertThat(connectionFactory.getAuthDescriptor().getMechs()).isEqualTo(FakeAuthDescriptorGenerator.FAKE_MECHS); assertThat(connectionFactory.getAuthWaitTime()).isEqualTo(9999L); } @Test public void getNamespacedKey_namespaceExpirationRequired_true() throws Exception { CacheNamespace cacheNamespace = new CacheNamespace("books", true); when(memcachedClient.incr(eq("hXm.books@"), eq(0L), anyLong(), eq(DEFAULT_NAMESPACE_SEQUENCE_EXPIRY_SECONDS))) .thenReturn(123L); String namspacedKey = spyMemcachedAdapter.getNamespacedKey(cacheNamespace, "books#1"); assertThat(namspacedKey).isEqualTo("hXm.books@123:books#1"); verify(memcachedClient).incr(eq("hXm.books@"), eq(0L), longCaptor.capture(), eq(DEFAULT_NAMESPACE_SEQUENCE_EXPIRY_SECONDS)); assertSystemCurrentTimeMillis(longCaptor.getValue()); } private void assertSystemCurrentTimeMillis(long value) { assertThat(value).isGreaterThanOrEqualTo(startTimeMills).isLessThanOrEqualTo(System.currentTimeMillis()); } @Test public void getNamespacedKey_namespaceExpirationRequired_false() throws Exception { CacheNamespace cacheNamespace = new CacheNamespace("authors", false); String namespacedKey = spyMemcachedAdapter.getNamespacedKey(cacheNamespace, "authors#4"); assertThat(namespacedKey).isEqualTo("hXm.authors:authors#4"); } @Test public void getNamespacedKey_no_cacheKeyPrefix() throws Exception { spyMemcachedAdapter.setCacheKeyPrefix(null); CacheNamespace cacheNamespace = new CacheNamespace("UpdateTimestamp", false); String namespacedKey = spyMemcachedAdapter.getNamespacedKey(cacheNamespace, "authors"); assertThat(namespacedKey).isEqualTo("UpdateTimestamp:authors"); } @Test public void destroy() throws Exception { spyMemcachedAdapter.destroy(); verify(memcachedClient).shutdown(); } private void givenNamespaceAndKey(CacheNamespace cacheNamespace, String key, String namespacedKey) { this.givenCacheNamespace = cacheNamespace; this.givenKey = key; this.givenNamespacedKey = namespacedKey; doReturn(givenNamespacedKey).when(spyMemcachedAdapter).getNamespacedKey(givenCacheNamespace, givenKey); } @Test public void get() throws Exception { givenNamespaceAndKey(new CacheNamespace("books", true), "book#1", "hXm.books@111:book#1"); String cachedValue = "cached value"; when(memcachedClient.get(givenNamespacedKey)).thenReturn(cachedValue); assertThat(spyMemcachedAdapter.get(givenCacheNamespace, givenKey)).isEqualTo(cachedValue); } @Test public void get_null() throws Exception { givenNamespaceAndKey(new CacheNamespace("authors", true), "authors#10", "hXm.authors@1:authors#10"); when(memcachedClient.get(givenNamespacedKey)).thenReturn(null); assertThat(spyMemcachedAdapter.get(givenCacheNamespace, givenKey)).isNull(); } @Test public void set() throws Exception { givenNamespaceAndKey(new CacheNamespace("authors", true), "authors#1223", "hXm.books@1234:authors#1223"); String value = "my value"; spyMemcachedAdapter.set(givenCacheNamespace, givenKey, value, 300); verify(memcachedClient).set(givenNamespacedKey, 300, value); } @Test public void delete() throws Exception { givenNamespaceAndKey(new CacheNamespace("StandardQueryCache", false), "laptops", "hXm.StandardQueryCache:laptops"); spyMemcachedAdapter.delete(givenCacheNamespace, givenKey); verify(memcachedClient).delete(givenNamespacedKey); } @Test public void increaseCounter() throws Exception { givenNamespaceAndKey(new CacheNamespace("MemcachedTimestamper", false), "timestamper", "hXm.MemcachedTimestamper:timestamper"); long by = 5L; long defaultValue = 1L; int expirySeconds = 123; when(memcachedClient.incr(givenNamespacedKey, by, defaultValue, expirySeconds)).thenReturn(98765L); long actual = spyMemcachedAdapter.increaseCounter(givenCacheNamespace, givenKey, by, defaultValue, expirySeconds); assertThat(actual).isEqualTo(98765L); } @Test public void getCounter() throws Exception { givenNamespaceAndKey(new CacheNamespace("MemcachedTimestamper", false), "timestamper", "hXm.MemcachedTimestamper:timestamper"); long defaultValue = 10L; int expirySeconds = 123; when(memcachedClient.incr(givenNamespacedKey, 0, defaultValue, expirySeconds)).thenReturn(100L); long actual = spyMemcachedAdapter.getCounter(givenCacheNamespace, givenKey, defaultValue, expirySeconds); assertThat(actual).isEqualTo(100L); } @Test public void evictAll_namespaceExpirationRequired_true() throws Exception { CacheNamespace cacheNamespace = new CacheNamespace("people", true); spyMemcachedAdapter.evictAll(cacheNamespace); verify(memcachedClient).incr(eq("hXm.people@"), eq(1), longCaptor.capture(), eq(DEFAULT_NAMESPACE_SEQUENCE_EXPIRY_SECONDS)); assertSystemCurrentTimeMillis(longCaptor.getValue()); } @Test public void evictAll_namespaceExpirationRequired_false() throws Exception { CacheNamespace cacheNamespace = new CacheNamespace("StandardQueryCache", false); spyMemcachedAdapter.evictAll(cacheNamespace); verify(memcachedClient, never()).incr(anyString(), anyInt(), anyLong(), anyInt()); } @Test public void evictAll_no_cacheKeyPrefix() throws Exception { spyMemcachedAdapter.setCacheKeyPrefix(""); CacheNamespace cacheNamespace = new CacheNamespace("people", true); spyMemcachedAdapter.evictAll(cacheNamespace); verify(memcachedClient).incr(eq("people@"), eq(1), longCaptor.capture(), eq(DEFAULT_NAMESPACE_SEQUENCE_EXPIRY_SECONDS)); assertSystemCurrentTimeMillis(longCaptor.getValue()); } public static class FakeTranscoder implements InitializableTranscoder<Object> { private boolean initialized = false; public boolean isInitialized() { return initialized; } @Override public boolean asyncDecode(CachedData d) { return false; } @Override public CachedData encode(Object o) { return null; } @Override public Object decode(CachedData d) { return null; } @Override public int getMaxSize() { return 0; } @Override public void init(OverridableReadOnlyProperties properties) { initialized = true; } } public static class FakeAuthDescriptorGenerator implements AuthDescriptorGenerator { public static final String[] FAKE_MECHS = new String[] {"fake", "mechs"}; @Override public AuthDescriptor generate(OverridableReadOnlyProperties properties) { return new AuthDescriptor(FAKE_MECHS, null); } } }