#![cfg(feature = "redis_store")]
use cached::time::Duration;
const CORRUPT: &[u8] = b"\xff\xff not a valid cached value \x00\x01\x02";
mod sync_tests {
use super::*;
use cached::{ConcurrentCached, RedisCache, SerializeCached};
fn try_build(prefix: &str, ttl: Option<Duration>) -> Option<RedisCache<String, String>> {
let mut b = RedisCache::<String, String>::builder(prefix).namespace("");
if let Some(t) = ttl {
b = b.ttl(t);
}
b.build().ok()
}
fn raw_conn(cache: &RedisCache<String, String>) -> redis::Connection {
redis::Client::open(cache.connection_string().reveal())
.expect("open redis client")
.get_connection()
.expect("redis connection")
}
fn raw_ttl_secs(cache: &RedisCache<String, String>, full_key: &str) -> i64 {
let mut conn = raw_conn(cache);
redis::cmd("TTL")
.arg(full_key)
.query(&mut conn)
.expect("TTL query")
}
#[test]
fn cache_set_ref_ttl_unset_uses_plain_set_no_expiry() {
let prefix = "v3_setref_sync_unset";
let Some(cache) = try_build(prefix, None) else {
eprintln!("skipping cache_set_ref_ttl_unset_uses_plain_set_no_expiry: no live redis");
return;
};
cache.cache_clear().expect("clear");
let key = "k".to_string();
let out: () = cache
.cache_set_ref(&key, &"v".to_string())
.expect("set_ref");
assert_eq!(out, ());
let ttl = raw_ttl_secs(&cache, ":v3_setref_sync_unset:k");
assert_eq!(
ttl, -1,
"ttl-unset cache_set_ref must store the key without expiry (raw TTL -1), got {ttl}"
);
assert_eq!(
cache.cache_get(&key).unwrap(),
Some("v".to_string()),
"the value written from a borrow must be readable"
);
cache.cache_clear().expect("cleanup");
}
#[test]
fn cache_set_ref_ttl_set_uses_psetex_expiry() {
let prefix = "v3_setref_sync_set";
let Some(cache) = try_build(prefix, Some(Duration::from_secs(60))) else {
eprintln!("skipping cache_set_ref_ttl_set_uses_psetex_expiry: no live redis");
return;
};
cache.cache_clear().expect("clear");
let key = "k".to_string();
cache
.cache_set_ref(&key, &"v".to_string())
.expect("set_ref");
let ttl = raw_ttl_secs(&cache, ":v3_setref_sync_set:k");
assert!(
ttl > 0,
"ttl-set cache_set_ref must apply a positive expiry via PSETEX, got {ttl}"
);
assert_eq!(
cache.cache_get(&key).unwrap(),
Some("v".to_string()),
"the value written from a borrow must be readable"
);
cache.cache_clear().expect("cleanup");
}
#[test]
fn cache_set_ref_over_corrupt_previous_returns_ok_unit() {
let prefix = "v3_setref_sync_corrupt";
let Some(cache) = try_build(prefix, Some(Duration::from_secs(60))) else {
eprintln!(
"skipping cache_set_ref_over_corrupt_previous_returns_ok_unit: no live redis"
);
return;
};
cache.cache_clear().expect("clear");
let key = "k".to_string();
let full_key = ":v3_setref_sync_corrupt:k";
let mut conn = raw_conn(&cache);
let _: () = redis::cmd("SET")
.arg(full_key)
.arg(CORRUPT)
.query(&mut conn)
.unwrap();
cache
.cache_set_ref(&key, &"fresh".to_string())
.expect("set_ref over corrupt previous must be Ok(())");
assert_eq!(
cache.cache_get(&key).unwrap(),
Some("fresh".to_string()),
"the fresh value written over the corrupt one must be readable"
);
cache.cache_clear().expect("cleanup");
}
}
#[cfg(feature = "redis_tokio")]
mod async_tests {
use super::*;
use cached::{AsyncRedisCache, ConcurrentCachedAsync, SerializeCachedAsync};
async fn try_build(
prefix: &str,
ttl: Option<Duration>,
) -> Option<AsyncRedisCache<String, String>> {
let mut b = AsyncRedisCache::<String, String>::builder(prefix).namespace("");
if let Some(t) = ttl {
b = b.ttl(t);
}
b.build().await.ok()
}
fn raw_ttl_secs(cache: &AsyncRedisCache<String, String>, full_key: &str) -> i64 {
let mut conn = redis::Client::open(cache.connection_string().reveal())
.expect("open redis client")
.get_connection()
.expect("redis connection");
redis::cmd("TTL")
.arg(full_key)
.query(&mut conn)
.expect("TTL query")
}
#[tokio::test]
async fn async_cache_set_ref_ttl_unset_uses_plain_set_no_expiry() {
let prefix = "v3_setref_async_unset";
let Some(cache) = try_build(prefix, None).await else {
eprintln!(
"skipping async_cache_set_ref_ttl_unset_uses_plain_set_no_expiry: no live redis"
);
return;
};
cache.async_cache_clear().await.expect("clear");
let key = "k".to_string();
let out: () = cache
.async_cache_set_ref(&key, &"v".to_string())
.await
.expect("set_ref");
assert_eq!(out, ());
let ttl = raw_ttl_secs(&cache, ":v3_setref_async_unset:k");
assert_eq!(
ttl, -1,
"ttl-unset async_cache_set_ref must store without expiry (raw TTL -1), got {ttl}"
);
assert_eq!(
cache.async_cache_get(&key).await.unwrap(),
Some("v".to_string())
);
cache.async_cache_clear().await.expect("cleanup");
}
#[tokio::test]
async fn async_cache_set_ref_ttl_set_uses_psetex_expiry() {
let prefix = "v3_setref_async_set";
let Some(cache) = try_build(prefix, Some(Duration::from_secs(60))).await else {
eprintln!("skipping async_cache_set_ref_ttl_set_uses_psetex_expiry: no live redis");
return;
};
cache.async_cache_clear().await.expect("clear");
let key = "k".to_string();
cache
.async_cache_set_ref(&key, &"v".to_string())
.await
.expect("set_ref");
let ttl = raw_ttl_secs(&cache, ":v3_setref_async_set:k");
assert!(
ttl > 0,
"ttl-set async_cache_set_ref must apply a positive expiry via PSETEX, got {ttl}"
);
assert_eq!(
cache.async_cache_get(&key).await.unwrap(),
Some("v".to_string())
);
cache.async_cache_clear().await.expect("cleanup");
}
}