/*
* Copyright 2016-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.core;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.util.ReflectionTestUtils.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisKeyValueAdapter.EnableKeyspaceEvents;
import org.springframework.data.redis.core.convert.Bucket;
import org.springframework.data.redis.core.convert.KeyspaceConfiguration;
import org.springframework.data.redis.core.convert.MappingConfiguration;
import org.springframework.data.redis.core.convert.RedisData;
import org.springframework.data.redis.core.convert.SimpleIndexedPropertyValue;
import org.springframework.data.redis.core.index.IndexConfiguration;
import org.springframework.data.redis.core.mapping.RedisMappingContext;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
/**
* Unit tests for {@link RedisKeyValueAdapter}.
*
* @author Christoph Strobl
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.Silent.class)
public class RedisKeyValueAdapterUnitTests {
RedisKeyValueAdapter adapter;
RedisTemplate<?, ?> template;
RedisMappingContext context;
@Mock JedisConnectionFactory jedisConnectionFactoryMock;
@Mock RedisConnection redisConnectionMock;
@Before
public void setUp() throws Exception {
template = new RedisTemplate<Object, Object>();
template.setConnectionFactory(jedisConnectionFactoryMock);
template.afterPropertiesSet();
when(jedisConnectionFactoryMock.getConnection()).thenReturn(redisConnectionMock);
when(redisConnectionMock.getConfig("notify-keyspace-events"))
.thenReturn(Arrays.asList("notify-keyspace-events", "KEA"));
context = new RedisMappingContext(new MappingConfiguration(new IndexConfiguration(), new KeyspaceConfiguration()));
context.afterPropertiesSet();
adapter = new RedisKeyValueAdapter(template, context);
adapter.afterPropertiesSet();
}
@After
public void tearDown() throws Exception {
adapter.destroy();
}
@Test // DATAREDIS-507
public void destroyShouldNotDestroyConnectionFactory() throws Exception {
adapter.destroy();
verify(jedisConnectionFactoryMock, never()).destroy();
}
@Test // DATAREDIS-512, DATAREDIS-530
public void putShouldRemoveExistingIndexValuesWhenUpdating() {
RedisData rd = new RedisData(Bucket.newBucketFromStringMap(Collections.singletonMap("_id", "1")));
rd.addIndexedData(new SimpleIndexedPropertyValue("persons", "firstname", "rand"));
when(redisConnectionMock.sMembers(Mockito.any(byte[].class)))
.thenReturn(new LinkedHashSet<byte[]>(Arrays.asList("persons:firstname:rand".getBytes())));
when(redisConnectionMock.del((byte[][]) any())).thenReturn(1L);
adapter.put("1", rd, "persons");
verify(redisConnectionMock, times(1)).sRem(Mockito.any(byte[].class),
Mockito.any(byte[].class));
}
@Test // DATAREDIS-512
public void putShouldNotTryToRemoveExistingIndexValuesWhenInsertingNew() {
RedisData rd = new RedisData(Bucket.newBucketFromStringMap(Collections.singletonMap("_id", "1")));
rd.addIndexedData(new SimpleIndexedPropertyValue("persons", "firstname", "rand"));
when(redisConnectionMock.sMembers(Mockito.any(byte[].class)))
.thenReturn(new LinkedHashSet<byte[]>(Arrays.asList("persons:firstname:rand".getBytes())));
when(redisConnectionMock.del((byte[][]) any())).thenReturn(0L);
adapter.put("1", rd, "persons");
verify(redisConnectionMock, never()).sRem(Mockito.any(byte[].class), (byte[][]) any());
}
@Test // DATAREDIS-491
public void shouldInitKeyExpirationListenerOnStartup() throws Exception {
adapter.destroy();
adapter = new RedisKeyValueAdapter(template, context);
adapter.setEnableKeyspaceEvents(EnableKeyspaceEvents.ON_STARTUP);
adapter.afterPropertiesSet();
KeyExpirationEventMessageListener listener = ((AtomicReference<KeyExpirationEventMessageListener>) getField(adapter,
"expirationListener")).get();
assertThat(listener, notNullValue());
}
@Test // DATAREDIS-491
public void shouldInitKeyExpirationListenerOnFirstPutWithTtl() throws Exception {
adapter.destroy();
adapter = new RedisKeyValueAdapter(template, context);
adapter.setEnableKeyspaceEvents(EnableKeyspaceEvents.ON_DEMAND);
adapter.afterPropertiesSet();
KeyExpirationEventMessageListener listener = ((AtomicReference<KeyExpirationEventMessageListener>) getField(adapter,
"expirationListener")).get();
assertThat(listener, nullValue());
adapter.put("should-NOT-start-listener", new WithoutTimeToLive(), "keyspace");
listener = ((AtomicReference<KeyExpirationEventMessageListener>) getField(adapter, "expirationListener")).get();
assertThat(listener, nullValue());
adapter.put("should-start-listener", new WithTimeToLive(), "keyspace");
listener = ((AtomicReference<KeyExpirationEventMessageListener>) getField(adapter, "expirationListener")).get();
assertThat(listener, notNullValue());
}
@Test // DATAREDIS-491
public void shouldNeverInitKeyExpirationListener() throws Exception {
adapter.destroy();
adapter = new RedisKeyValueAdapter(template, context);
adapter.afterPropertiesSet();
KeyExpirationEventMessageListener listener = ((AtomicReference<KeyExpirationEventMessageListener>) getField(adapter,
"expirationListener")).get();
assertThat(listener, nullValue());
adapter.put("should-NOT-start-listener", new WithoutTimeToLive(), "keyspace");
listener = ((AtomicReference<KeyExpirationEventMessageListener>) getField(adapter, "expirationListener")).get();
assertThat(listener, nullValue());
adapter.put("should-start-listener", new WithTimeToLive(), "keyspace");
listener = ((AtomicReference<KeyExpirationEventMessageListener>) getField(adapter, "expirationListener")).get();
assertThat(listener, nullValue());
}
static class WithoutTimeToLive {
@Id String id;
}
@RedisHash(timeToLive = 10)
static class WithTimeToLive {
@Id String id;
}
}