use ngdp_cache::{cdn::CdnCache, generic::GenericCache, ribbit::RibbitCache};
use std::time::Duration;
const TEST_CONFIG_DATA: &[u8] =
b"root = 9e3dfbafb41949c8cb14e0bc0055d225 70c91468bb187cc2b3d045d476c6899f
encoding = e468c86f90cd051195a3c5f8b08d7bd7 12ad2799f3e1ee9a9b5620e43a0d2b75
install = 17adc9e821c34e06ba6f4568aab0c040 9a127c8076a2c1b24fa3a97b0f5346d8
download = f2c3b74f3c51db3a5c4e2d87c52a0c82 24e1cd9ec87419dd826e991fa141c6e0";
const TEST_ARCHIVE_DATA: &[u8] =
b"BLTE\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00This is test archive data";
const TEST_RIBBIT_RESPONSE: &[u8] =
b"Region!STRING:0|BuildConfig!HEX:32|CDNConfig!HEX:32|BuildId!DEC:10|VersionsName!STRING:0
## seqn = 3016450
us|be2bb98dc28aee05bbee519393696cdb|fac77b9ca52c84ac28ad83a7dbe1c829|61491|11.1.7.61491";
#[tokio::test]
async fn test_cache_directory_creation() {
let generic = GenericCache::new().await.unwrap();
assert!(generic.base_dir().exists());
let cdn = CdnCache::new().await.unwrap();
assert!(cdn.base_dir().exists());
let ribbit = RibbitCache::new().await.unwrap();
assert!(ribbit.base_dir().exists());
}
#[tokio::test]
async fn test_cross_cache_isolation() {
let generic = GenericCache::new().await.unwrap();
let cdn = CdnCache::new().await.unwrap();
generic.write("test_key", b"generic data").await.unwrap();
assert!(!cdn.has_config("test_key").await);
assert!(!cdn.has_data("test_key").await);
generic.delete("test_key").await.unwrap();
}
#[tokio::test]
async fn test_cdn_cache_workflow() {
let cdn = CdnCache::new().await.unwrap();
let build_config_hash = "be2bb98dc28aee05bbee519393696cdb";
let cdn_config_hash = "fac77b9ca52c84ac28ad83a7dbe1c829";
let index_hash = "0052ea9a56fd7b3b6fe7d1d906e6cdef";
cdn.write_config(build_config_hash, TEST_CONFIG_DATA)
.await
.unwrap();
cdn.write_config(
cdn_config_hash,
b"archives = 8a41b9e8bf2d85ad73e087c446c655fb",
)
.await
.unwrap();
cdn.write_index(index_hash, b"binary index data")
.await
.unwrap();
let data_hash = "1234567890abcdef1234567890abcdef";
cdn.write_data(data_hash, b"game data").await.unwrap();
assert!(cdn.has_config(build_config_hash).await);
assert!(cdn.has_config(cdn_config_hash).await);
assert!(cdn.has_index(index_hash).await);
assert!(cdn.has_data(data_hash).await);
let config_data = cdn.read_config(build_config_hash).await.unwrap();
assert_eq!(config_data, TEST_CONFIG_DATA);
tokio::fs::remove_file(cdn.config_path(build_config_hash))
.await
.ok();
tokio::fs::remove_file(cdn.config_path(cdn_config_hash))
.await
.ok();
tokio::fs::remove_file(cdn.index_path(index_hash))
.await
.ok();
tokio::fs::remove_file(cdn.data_path(data_hash)).await.ok();
}
#[tokio::test]
async fn test_cdn_cache_product_separation() {
let wow_cache = CdnCache::for_product("wow").await.unwrap();
let d4_cache = CdnCache::for_product("d4").await.unwrap();
let data_hash = "deadbeef1234567890abcdef12345678";
wow_cache
.write_data(data_hash, TEST_ARCHIVE_DATA)
.await
.unwrap();
assert!(!d4_cache.has_data(data_hash).await);
assert!(wow_cache.has_data(data_hash).await);
tokio::fs::remove_file(wow_cache.data_path(data_hash))
.await
.ok();
}
#[tokio::test]
async fn test_cdn_cache_streaming() {
let cdn = CdnCache::new().await.unwrap();
let data_hash = "streamtest1234567890abcdef123456";
let large_data = vec![0u8; 1024 * 1024]; cdn.write_data(data_hash, &large_data).await.unwrap();
let mut file = cdn.open_data(data_hash).await.unwrap();
let mut buffer = Vec::new();
tokio::io::AsyncReadExt::read_to_end(&mut file, &mut buffer)
.await
.unwrap();
assert_eq!(buffer.len(), large_data.len());
tokio::fs::remove_file(cdn.data_path(data_hash)).await.ok();
}
#[tokio::test]
async fn test_ribbit_cache_expiration() {
let cache = RibbitCache::with_ttl(Duration::from_secs(1)).await.unwrap();
let region = "us";
let product = "wow_test";
let endpoint = "versions_test";
cache
.write(region, product, endpoint, TEST_RIBBIT_RESPONSE)
.await
.unwrap();
let cache_path = cache.cache_path(region, product, endpoint);
let meta_path = cache.metadata_path(region, product, endpoint);
assert!(cache_path.exists(), "Cache file should exist after write");
assert!(meta_path.exists(), "Metadata file should exist after write");
assert!(
cache.is_valid(region, product, endpoint).await,
"Cache should be valid immediately after write"
);
tokio::time::sleep(Duration::from_secs(2)).await;
assert!(
!cache.is_valid(region, product, endpoint).await,
"Cache should be expired after TTL"
);
cache.clear_expired().await.unwrap();
assert!(
!cache_path.exists(),
"Cache file should be deleted after clear_expired"
);
assert!(
!meta_path.exists(),
"Metadata file should be deleted after clear_expired"
);
}
#[tokio::test]
async fn test_concurrent_cache_access() {
let _cache = GenericCache::new().await.unwrap();
let mut handles = vec![];
for i in 0..10 {
let cache_clone = GenericCache::new().await.unwrap();
let handle = tokio::spawn(async move {
let key = format!("concurrent_key_{i}");
let data = format!("data_{i}");
cache_clone.write(&key, data.as_bytes()).await.unwrap();
let read_data = cache_clone.read(&key).await.unwrap();
assert_eq!(read_data, data.as_bytes());
cache_clone.delete(&key).await.unwrap();
});
handles.push(handle);
}
for handle in handles {
handle.await.unwrap();
}
}
#[tokio::test]
async fn test_cache_corruption_detection() {
let cache = GenericCache::new().await.unwrap();
let key = "corruption_test";
cache.write(key, b"valid data").await.unwrap();
let path = cache.get_path(key);
tokio::fs::write(&path, b"").await.unwrap();
let data = cache.read(key).await.unwrap();
assert_eq!(data.len(), 0);
cache.delete(key).await.unwrap();
}
#[tokio::test]
async fn test_cache_key_validation() {
let cache = GenericCache::new().await.unwrap();
let test_keys = vec![
"simple_key",
"key_with_numbers_123",
"key-with-dashes",
"key.with.dots",
"UPPERCASE_KEY",
"key/with/slashes", ];
for key in test_keys {
let data = format!("data for {key}").into_bytes();
cache.write(key, &data).await.unwrap();
let read_data = cache.read(key).await.unwrap();
assert_eq!(read_data, data);
cache.delete(key).await.unwrap();
}
}
#[tokio::test]
async fn test_large_file_handling() {
let cdn = CdnCache::new().await.unwrap();
let large_hash = "largefiletest567890abcdef1234567";
let size = 10 * 1024 * 1024; let large_data = vec![42u8; size];
cdn.write_data(large_hash, &large_data).await.unwrap();
let reported_size = cdn.data_size(large_hash).await.unwrap();
assert_eq!(reported_size, size as u64);
let read_data = cdn.read_data(large_hash).await.unwrap();
assert_eq!(read_data.len(), size);
assert_eq!(read_data[0], 42);
assert_eq!(read_data[size - 1], 42);
tokio::fs::remove_file(cdn.data_path(large_hash)).await.ok();
}
#[tokio::test]
async fn test_cache_clear_operations() {
let cache = GenericCache::with_subdirectory("clear_test").await.unwrap();
for i in 0..5 {
let key = format!("clear_key_{i}");
cache.write(&key, b"data").await.unwrap();
}
for i in 0..5 {
let key = format!("clear_key_{i}");
assert!(cache.exists(&key).await);
}
cache.clear().await.unwrap();
for i in 0..5 {
let key = format!("clear_key_{i}");
assert!(!cache.exists(&key).await);
}
}
#[tokio::test]
async fn test_nested_directory_creation() {
let cdn = CdnCache::new().await.unwrap();
let deeply_nested_hash = "abcdef0123456789abcdef0123456789";
cdn.write_data(deeply_nested_hash, b"nested data")
.await
.unwrap();
assert!(cdn.has_data(deeply_nested_hash).await);
let data = cdn.read_data(deeply_nested_hash).await.unwrap();
assert_eq!(data, b"nested data");
tokio::fs::remove_file(cdn.data_path(deeply_nested_hash))
.await
.ok();
}