137 lines
4.7 KiB
Java
137 lines
4.7 KiB
Java
|
|
package com.njcn.middle.cache.template;
|
||
|
|
|
||
|
|
import com.alibaba.fastjson.JSON;
|
||
|
|
import com.njcn.middle.cache.autoconfig.RedisCacheProperties;
|
||
|
|
import com.njcn.middle.cache.constant.CacheConstant;
|
||
|
|
import com.njcn.middle.cache.support.CacheKeyBuilder;
|
||
|
|
import com.njcn.middle.cache.support.CacheValueCodec;
|
||
|
|
import lombok.AllArgsConstructor;
|
||
|
|
import lombok.Data;
|
||
|
|
import lombok.NoArgsConstructor;
|
||
|
|
import org.junit.jupiter.api.BeforeEach;
|
||
|
|
import org.junit.jupiter.api.Test;
|
||
|
|
import org.mockito.Mock;
|
||
|
|
import org.mockito.MockitoAnnotations;
|
||
|
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||
|
|
import org.springframework.data.redis.core.ValueOperations;
|
||
|
|
|
||
|
|
import java.util.concurrent.TimeUnit;
|
||
|
|
|
||
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||
|
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||
|
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||
|
|
import static org.mockito.ArgumentMatchers.any;
|
||
|
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||
|
|
import static org.mockito.ArgumentMatchers.eq;
|
||
|
|
import static org.mockito.Mockito.lenient;
|
||
|
|
import static org.mockito.Mockito.never;
|
||
|
|
import static org.mockito.Mockito.verify;
|
||
|
|
import static org.mockito.Mockito.when;
|
||
|
|
|
||
|
|
class RedisCacheEnhanceTemplateTest {
|
||
|
|
|
||
|
|
@Mock
|
||
|
|
StringRedisTemplate redis;
|
||
|
|
@Mock
|
||
|
|
ValueOperations<String, String> valueOps;
|
||
|
|
|
||
|
|
CacheValueCodec codec = new CacheValueCodec();
|
||
|
|
CacheKeyBuilder keyBuilder = new CacheKeyBuilder("cache:", false, "");
|
||
|
|
RedisCacheProperties props = new RedisCacheProperties();
|
||
|
|
RedisCacheEnhanceTemplate template;
|
||
|
|
|
||
|
|
@Data
|
||
|
|
@AllArgsConstructor
|
||
|
|
@NoArgsConstructor
|
||
|
|
static class User {
|
||
|
|
private Long id;
|
||
|
|
private String name;
|
||
|
|
}
|
||
|
|
|
||
|
|
@BeforeEach
|
||
|
|
void init() {
|
||
|
|
MockitoAnnotations.initMocks(this);
|
||
|
|
lenient().when(redis.opsForValue()).thenReturn(valueOps);
|
||
|
|
template = new RedisCacheEnhanceTemplate(redis, keyBuilder, codec, props);
|
||
|
|
}
|
||
|
|
|
||
|
|
@Test
|
||
|
|
void setSerializesWithExactTtlWhenNoJitter() {
|
||
|
|
props.setJitterRatio(0);
|
||
|
|
User u = new User(1L, "a");
|
||
|
|
template.set("user:1", u, 100);
|
||
|
|
verify(valueOps).set("cache:user:1", JSON.toJSONString(u), 100L, TimeUnit.SECONDS);
|
||
|
|
}
|
||
|
|
|
||
|
|
@Test
|
||
|
|
void getHitDeserializes() {
|
||
|
|
when(valueOps.get("cache:user:1")).thenReturn(JSON.toJSONString(new User(1L, "a")));
|
||
|
|
User got = template.get("user:1", User.class);
|
||
|
|
assertEquals("a", got.getName());
|
||
|
|
}
|
||
|
|
|
||
|
|
@Test
|
||
|
|
void getMissReturnsNull() {
|
||
|
|
when(valueOps.get("cache:user:1")).thenReturn(null);
|
||
|
|
assertNull(template.get("user:1", User.class));
|
||
|
|
}
|
||
|
|
|
||
|
|
@Test
|
||
|
|
void getNullSentinelReturnsNull() {
|
||
|
|
when(valueOps.get("cache:user:1")).thenReturn(CacheConstant.NULL_SENTINEL);
|
||
|
|
assertNull(template.get("user:1", User.class));
|
||
|
|
}
|
||
|
|
|
||
|
|
@Test
|
||
|
|
void getDirtyDataThrowsIllegalState() {
|
||
|
|
when(valueOps.get("cache:user:1")).thenReturn("{not-json");
|
||
|
|
assertThrows(IllegalStateException.class, () -> template.get("user:1", User.class));
|
||
|
|
}
|
||
|
|
|
||
|
|
@Test
|
||
|
|
void expireDelegatesWithJitterTtl() {
|
||
|
|
props.setJitterRatio(0);
|
||
|
|
when(redis.expire("cache:user:1", 200L, TimeUnit.SECONDS)).thenReturn(true);
|
||
|
|
assertTrue(template.expire("user:1", 200));
|
||
|
|
verify(redis).expire("cache:user:1", 200L, TimeUnit.SECONDS);
|
||
|
|
}
|
||
|
|
|
||
|
|
@Test
|
||
|
|
void applyJitterWithinBounds() {
|
||
|
|
props.setJitterRatio(0.1);
|
||
|
|
long bound = (long) (100 * 0.1);
|
||
|
|
for (int i = 0; i < 50; i++) {
|
||
|
|
long ttl = template.applyJitter(100);
|
||
|
|
assertTrue(ttl >= 100 && ttl <= 100 + bound, "ttl=" + ttl);
|
||
|
|
}
|
||
|
|
props.setJitterRatio(0);
|
||
|
|
assertEquals(100, template.applyJitter(100));
|
||
|
|
}
|
||
|
|
|
||
|
|
@Test
|
||
|
|
void slidingOffDoesNotRenewOnGet() {
|
||
|
|
when(valueOps.get("cache:user:1")).thenReturn(JSON.toJSONString(new User(1L, "a")));
|
||
|
|
template.get("user:1", User.class);
|
||
|
|
verify(redis, never()).expire(any(), anyLong(), any());
|
||
|
|
}
|
||
|
|
|
||
|
|
@Test
|
||
|
|
void slidingOnRenewsRealValueOnGet() {
|
||
|
|
props.setSlidingExpireEnabled(true);
|
||
|
|
props.setJitterRatio(0);
|
||
|
|
when(valueOps.get("cache:user:1")).thenReturn(JSON.toJSONString(new User(1L, "a")));
|
||
|
|
when(redis.expire(eq("cache:user:1"), anyLong(), eq(TimeUnit.SECONDS))).thenReturn(true);
|
||
|
|
template.get("user:1", User.class);
|
||
|
|
verify(redis).expire("cache:user:1", props.getDefaultTtlSeconds(), TimeUnit.SECONDS);
|
||
|
|
}
|
||
|
|
|
||
|
|
@Test
|
||
|
|
void slidingOnDoesNotRenewNullSentinel() {
|
||
|
|
props.setSlidingExpireEnabled(true);
|
||
|
|
when(valueOps.get("cache:user:1")).thenReturn(CacheConstant.NULL_SENTINEL);
|
||
|
|
assertNull(template.get("user:1", User.class));
|
||
|
|
verify(redis, never()).expire(any(), anyLong(), any());
|
||
|
|
}
|
||
|
|
}
|