#![cfg(feature = "cache-redis")]
use rs_zero::cache_redis::{
RedisCacheError, RedisClusterRedirect, RedisClusterRedirectKind, RedisClusterRouteAction,
RedisClusterSlotMap, RedisCommandEvent, RedisCommandEventKind, RedisCommandOutcome,
classify_redis_error, redis_cluster_hash_tag, redis_cluster_slot,
};
#[test]
fn cluster_slot_matches_redis_known_vectors_and_hash_tags() {
assert_eq!(redis_cluster_slot("123456789"), 12739);
assert_eq!(redis_cluster_slot("foo{bar}zap"), redis_cluster_slot("bar"));
assert_eq!(redis_cluster_slot("{user1000}.following"), 3443);
assert_eq!(redis_cluster_slot("{user1000}.followers"), 3443);
assert_eq!(redis_cluster_hash_tag("foo{}{bar}"), "foo{}{bar}");
assert_eq!(redis_cluster_hash_tag("foo{{bar}}zap"), "{bar");
assert_eq!(redis_cluster_hash_tag("foo{bar}zap"), "bar");
}
#[test]
fn moved_and_ask_errors_are_parsed_without_losing_target() {
let moved = RedisClusterRedirect::parse("MOVED 3999 10.0.0.2:6381").expect("moved");
assert_eq!(moved.kind, RedisClusterRedirectKind::Moved);
assert_eq!(moved.slot, 3999);
assert_eq!(moved.endpoint, "10.0.0.2:6381");
let ask = RedisClusterRedirect::parse("ASK 4000 cache.example:6379").expect("ask");
assert_eq!(ask.kind, RedisClusterRedirectKind::Ask);
assert_eq!(ask.slot, 4000);
assert_eq!(ask.endpoint, "cache.example:6379");
let error = RedisCacheError::Backend("MOVED 3999 10.0.0.2:6381".to_string());
assert_eq!(
classify_redis_error(&error),
RedisCommandOutcome::Redirect(RedisClusterRedirectKind::Moved)
);
assert!(RedisClusterRedirect::parse("MOVED missing").is_none());
}
#[test]
fn route_action_updates_slot_map_for_moved_and_keeps_ask_temporary() {
let mut map = RedisClusterSlotMap::default();
let moved = RedisClusterRedirect::parse("MOVED 3999 10.0.0.2:6381").expect("moved");
let ask = RedisClusterRedirect::parse("ASK 4000 10.0.0.3:6381").expect("ask");
assert_eq!(
map.apply_redirect(&moved),
RedisClusterRouteAction::RefreshSlot
);
assert_eq!(map.endpoint_for_slot(3999), Some("10.0.0.2:6381"));
assert_eq!(
map.apply_redirect(&ask),
RedisClusterRouteAction::AskRedirect
);
assert_eq!(map.endpoint_for_slot(4000), None);
}
#[test]
fn redirect_limit_returns_explicit_error() {
let error = RedisCacheError::too_many_cluster_redirects(5, "rs-zero:{users}:42");
assert!(
error
.to_string()
.contains("too many redis cluster redirects")
);
assert_eq!(classify_redis_error(&error), RedisCommandOutcome::Error);
assert!(!error.to_string().contains("password"));
}
#[test]
fn command_events_are_low_cardinality() {
let redirect = RedisCommandEvent::redirect(RedisClusterRedirectKind::Ask);
let pool = RedisCommandEvent::new(RedisCommandEventKind::Pool, RedisCommandOutcome::Success);
assert_eq!(redirect.event.as_str(), "redirect");
assert_eq!(redirect.outcome.as_str(), "ask");
assert_eq!(pool.event.as_str(), "pool");
assert_eq!(pool.outcome.as_str(), "success");
}
#[test]
fn cluster_config_builds_native_cluster_client_without_connecting() {
let config = rs_zero::cache_redis::RedisCacheConfig {
url: "redis://127.0.0.1:7000, redis://127.0.0.1:7001".to_string(),
cluster: rs_zero::cache_redis::RedisClusterConfig {
enabled: true,
max_redirects: 8,
read_from_replicas: true,
},
..rs_zero::cache_redis::RedisCacheConfig::default()
};
let store = rs_zero::cache_redis::RedisCacheStore::new(config).expect("cluster store");
assert_eq!(store.namespace(), "rs-zero");
assert!(format!("{store:?}").contains("RedisCacheBackend::Cluster"));
}
#[test]
fn cluster_startup_nodes_parse_comma_separated_urls() {
let nodes = rs_zero::cache_redis::cluster_startup_nodes(
"redis://127.0.0.1:7000, redis://127.0.0.1:7001",
)
.expect("nodes");
assert_eq!(
nodes,
vec!["redis://127.0.0.1:7000", "redis://127.0.0.1:7001"]
);
}