#[cfg(feature = "redis")]
mod tests {
use std::env;
use std::time::Duration;
use serde::{Deserialize, Serialize};
use umbral_cache::Cache;
fn redis_url() -> Option<String> {
env::var("REDIS_URL").ok()
}
fn key(suffix: &str) -> String {
format!("umbral:test:{}", suffix)
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Page {
title: String,
views: u64,
}
#[tokio::test]
async fn redis_connect_succeeds() {
let Some(url) = redis_url() else {
eprintln!("REDIS_URL not set — skipping redis_connect_succeeds");
return;
};
let result = Cache::redis(&url).await;
assert!(
result.is_ok(),
"connection should succeed: {:?}",
result.err()
);
}
#[tokio::test]
async fn redis_connect_fails_on_bad_url() {
let result = Cache::redis("redis://localhost:9999").await;
let _ = result; }
#[tokio::test]
async fn redis_set_get_string_round_trip() {
let Some(url) = redis_url() else {
eprintln!("REDIS_URL not set — skipping");
return;
};
let cache = Cache::redis(&url).await.unwrap();
let k = key("string_rt");
cache.set(&k, "hello redis", None).await.unwrap();
let v: Option<String> = cache.get(&k).await;
assert_eq!(v.as_deref(), Some("hello redis"));
cache.delete(&k).await;
}
#[tokio::test]
async fn redis_set_get_struct_round_trip() {
let Some(url) = redis_url() else {
eprintln!("REDIS_URL not set — skipping");
return;
};
let cache = Cache::redis(&url).await.unwrap();
let k = key("struct_rt");
let page = Page {
title: "Umbral Redis".into(),
views: 42,
};
cache.set(&k, &page, None).await.unwrap();
let back: Option<Page> = cache.get(&k).await;
assert_eq!(back, Some(page));
cache.delete(&k).await;
}
#[tokio::test]
async fn redis_get_miss_returns_none() {
let Some(url) = redis_url() else {
eprintln!("REDIS_URL not set — skipping");
return;
};
let cache = Cache::redis(&url).await.unwrap();
let v: Option<String> = cache.get(&key("definitely_absent_xyzzy")).await;
assert!(v.is_none());
}
#[tokio::test]
async fn redis_ttl_expires_the_entry() {
let Some(url) = redis_url() else {
eprintln!("REDIS_URL not set — skipping");
return;
};
let cache = Cache::redis(&url).await.unwrap();
let k = key("ttl_expire");
cache
.set(&k, "ephemeral", Some(Duration::from_secs(1)))
.await
.unwrap();
let early: Option<String> = cache.get(&k).await;
assert_eq!(
early.as_deref(),
Some("ephemeral"),
"should be present before TTL"
);
tokio::time::sleep(Duration::from_millis(1200)).await;
let late: Option<String> = cache.get(&k).await;
assert!(late.is_none(), "should be absent after TTL; got {late:?}");
}
#[tokio::test]
async fn redis_delete_evicts_the_key() {
let Some(url) = redis_url() else {
eprintln!("REDIS_URL not set — skipping");
return;
};
let cache = Cache::redis(&url).await.unwrap();
let k = key("delete_evict");
cache.set(&k, "to-be-deleted", None).await.unwrap();
assert!(cache.get::<String>(&k).await.is_some());
cache.delete(&k).await;
let after: Option<String> = cache.get(&k).await;
assert!(after.is_none(), "key should be gone after delete");
}
#[tokio::test]
async fn redis_clear_removes_all_keys_in_db() {
let Some(url) = redis_url() else {
eprintln!("REDIS_URL not set — skipping");
return;
};
if !url.ends_with("/15") {
eprintln!("redis_clear test only runs against DB /15 — skipping");
return;
}
let cache = Cache::redis(&url).await.unwrap();
cache.set(&key("clear_a"), "1", None).await.unwrap();
cache.set(&key("clear_b"), "2", None).await.unwrap();
cache.clear().await;
assert!(cache.get::<String>(&key("clear_a")).await.is_none());
assert!(cache.get::<String>(&key("clear_b")).await.is_none());
}
}