#![cfg(feature = "cache-redis")]
use std::time::Duration;
use rs_zero::{
cache::{CacheKey, CacheStore},
cache_redis::{
RedisCacheConfig, RedisCacheStore, RedisClusterRedirect, RedisClusterRouter,
RedisUnavailablePolicy,
},
};
#[tokio::test]
#[ignore = "requires RS_ZERO_TEST_REDIS_URL pointing to a running Redis instance"]
async fn redis_single_node_cache_roundtrip_and_degradation_config() {
let url = std::env::var("RS_ZERO_TEST_REDIS_URL")
.unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
let store = RedisCacheStore::new(RedisCacheConfig {
url,
namespace: format!("rs-zero-redis-hardening-{}", unique_suffix()),
unavailable_policy: RedisUnavailablePolicy::fail_open_for_cache(),
..RedisCacheConfig::default()
})
.expect("store");
store.health_check().await.expect("health");
let key = CacheKey::new(store.namespace(), ["cluster", "roundtrip"]);
store
.set_raw(&key, b"ok".to_vec(), Some(Duration::from_secs(5)))
.await
.expect("set");
assert_eq!(
store.get_raw(&key).await.expect("get"),
Some(b"ok".to_vec())
);
store.delete(&key).await.expect("delete");
}
#[tokio::test]
#[ignore = "requires RS_ZERO_TEST_REDIS_CLUSTER_URL pointing to a Redis Cluster node"]
async fn redis_cluster_env_is_available_for_manual_moved_ask_validation() {
let url = std::env::var("RS_ZERO_TEST_REDIS_CLUSTER_URL")
.expect("RS_ZERO_TEST_REDIS_CLUSTER_URL is required");
let store = RedisCacheStore::new(RedisCacheConfig {
url,
namespace: format!("rs-zero-redis-cluster-{}", unique_suffix()),
cluster: rs_zero::cache_redis::RedisClusterConfig {
enabled: true,
max_redirects: 8,
read_from_replicas: false,
},
..RedisCacheConfig::default()
})
.expect("cluster store");
store.health_check().await.expect("cluster health");
let first = CacheKey::new(store.namespace(), ["{manual-cluster-a}", "value"]);
let second = CacheKey::new(store.namespace(), ["{manual-cluster-b}", "value"]);
store
.set_raw(&first, b"first".to_vec(), Some(Duration::from_secs(5)))
.await
.expect("set first");
store
.set_raw(&second, b"second".to_vec(), Some(Duration::from_secs(5)))
.await
.expect("set second");
assert_eq!(
store.get_raw(&first).await.expect("get first"),
Some(b"first".to_vec())
);
assert_eq!(
store.get_raw(&second).await.expect("get second"),
Some(b"second".to_vec())
);
store
.delete_many(&[first.clone(), second.clone()])
.await
.expect("delete many");
assert!(
store
.get_raw(&first)
.await
.expect("get deleted first")
.is_none()
);
assert!(
store
.get_raw(&second)
.await
.expect("get deleted second")
.is_none()
);
let mut router = RedisClusterRouter::new(8);
let redirect = RedisClusterRedirect::parse(&format!(
"MOVED {} 127.0.0.1:6379",
RedisClusterRouter::slot_for_key(&first)
))
.expect("redirect parse");
router.apply_redirect(&redirect);
assert!(router.endpoint_for_key(&first).is_some());
}
fn unique_suffix() -> String {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
format!("{nanos}-{}", std::process::id())
}