#![cfg(feature = "bloom-filter")]
use std::time::Duration;
use oxcache::backend::interface::{CacheReader, CacheWriter, SyncCacheReader, SyncCacheWriter};
use oxcache::backend::MokaMemoryBackend;
use oxcache::features::bloom_filter::{BloomFilter, BloomFilterBackend};
#[test]
fn test_bloom_filter_basic_lifecycle() {
let bf = BloomFilter::new(10_000, 0.01);
assert!(!bf.contains("missing"));
bf.insert("key1");
bf.insert("key2");
assert!(bf.contains("key1"));
assert!(bf.contains("key2"));
bf.clear();
assert!(!bf.contains("key1"));
assert!(!bf.contains("key2"));
}
#[test]
fn test_bloom_filter_no_false_negatives() {
let bf = BloomFilter::new(10_000, 0.01);
for i in 0..1000 {
bf.insert(&format!("key_{}", i));
}
for i in 0..1000 {
assert!(
bf.contains(&format!("key_{}", i)),
"BF must not have false negatives for key_{}",
i
);
}
}
#[tokio::test]
async fn test_bf_backend_get_miss_skips_inner() {
let inner = MokaMemoryBackend::new();
let inner_ref = inner.clone();
let backend = BloomFilterBackend::new(inner);
let value = CacheReader::get(&backend, "missing").await.unwrap();
assert_eq!(value, None);
assert_eq!(CacheReader::get(&inner_ref, "missing").await.unwrap(), None);
}
#[tokio::test]
async fn test_bf_backend_set_updates_bloom_and_inner() {
let inner = MokaMemoryBackend::new();
let inner_ref = inner.clone();
let backend = BloomFilterBackend::new(inner);
CacheWriter::set(&backend, "k", b"v".to_vec(), None).await.unwrap();
assert!(backend.bloom().contains("k"));
assert_eq!(CacheReader::get(&inner_ref, "k").await.unwrap(), Some(b"v".to_vec()));
assert_eq!(CacheReader::get(&backend, "k").await.unwrap(), Some(b"v".to_vec()));
}
#[tokio::test]
async fn test_bf_backend_delete_does_not_modify_bloom() {
let inner = MokaMemoryBackend::new();
let backend = BloomFilterBackend::new(inner);
CacheWriter::set(&backend, "k", b"v".to_vec(), None).await.unwrap();
assert!(backend.bloom().contains("k"));
CacheWriter::delete(&backend, "k").await.unwrap();
assert!(
backend.bloom().contains("k"),
"BF should still contain key after delete"
);
assert_eq!(CacheReader::get(&backend, "k").await.unwrap(), None);
}
#[tokio::test]
async fn test_bf_backend_set_with_ttl_passes_through() {
let inner = MokaMemoryBackend::new();
let inner_ref = inner.clone();
let backend = BloomFilterBackend::new(inner);
CacheWriter::set(&backend, "k", b"v".to_vec(), Some(Duration::from_millis(50)))
.await
.unwrap();
assert_eq!(CacheReader::get(&backend, "k").await.unwrap(), Some(b"v".to_vec()));
tokio::time::sleep(Duration::from_millis(100)).await;
let mut expired = false;
for _ in 0..10 {
if CacheReader::get(&backend, "k").await.unwrap().is_none() {
expired = true;
break;
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
assert!(expired, "BF backend should return None after TTL expires");
let mut inner_expired = false;
for _ in 0..10 {
if CacheReader::get(&inner_ref, "k").await.unwrap().is_none() {
inner_expired = true;
break;
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
assert!(inner_expired, "inner backend should also expire after TTL");
}
#[tokio::test]
async fn test_bf_backend_clear_clears_both() {
let inner = MokaMemoryBackend::new();
let inner_ref = inner.clone();
let backend = BloomFilterBackend::new(inner);
CacheWriter::set(&backend, "k1", b"v1".to_vec(), None).await.unwrap();
CacheWriter::set(&backend, "k2", b"v2".to_vec(), None).await.unwrap();
CacheWriter::clear(&backend).await.unwrap();
assert!(!backend.bloom().contains("k1"));
assert!(!backend.bloom().contains("k2"));
assert_eq!(CacheReader::get(&inner_ref, "k1").await.unwrap(), None);
assert_eq!(CacheReader::get(&inner_ref, "k2").await.unwrap(), None);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_bf_backend_sync_get_set_basic() {
let inner = MokaMemoryBackend::new();
let backend = BloomFilterBackend::new(inner);
SyncCacheWriter::set(&backend, "k", b"v".to_vec(), None).unwrap();
let value = SyncCacheReader::get(&backend, "k").unwrap();
assert_eq!(value, Some(b"v".to_vec()));
assert!(backend.bloom().contains("k"));
let value = SyncCacheReader::get(&backend, "missing").unwrap();
assert_eq!(value, None);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_bf_backend_sync_set_with_ttl_passes_through() {
let inner = MokaMemoryBackend::new();
let backend = BloomFilterBackend::new(inner);
SyncCacheWriter::set(&backend, "k", b"v".to_vec(), Some(Duration::from_millis(50))).unwrap();
let value = SyncCacheReader::get(&backend, "k").unwrap();
assert_eq!(value, Some(b"v".to_vec()));
tokio::time::sleep(Duration::from_millis(100)).await;
let mut expired = false;
for _ in 0..10 {
if SyncCacheReader::get(&backend, "k").unwrap().is_none() {
expired = true;
break;
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
assert!(expired, "BF backend sync get should return None after TTL expires");
}