mod config;
mod object;
mod cache;
mod error;
mod utils;
pub use config::{CacheConfig, CachePathConfig, CacheFormatConfig};
pub use object::CacheObject;
pub use cache::Cache;
pub use error::CacheError;
pub type CacheResult<T> = std::result::Result<T, CacheError>;
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_cache_config_default() {
let config = CacheConfig::default();
assert_eq!(config.max_size, 0);
assert_eq!(config.max_files, 0);
assert!(!config.path.windows.is_empty());
assert!(!config.path.linux.is_empty());
assert!(!config.format.filename.is_empty());
assert!(!config.format.time.is_empty());
}
#[test]
fn test_cache_config_from_json() {
let json = r#"{
"path": {
"windows": "%temp%/TestCache",
"linux": "/tmp/testcache"
},
"format": {
"filename": "test_{name}.cache",
"time": "%Y%m%d"
},
"max_size": 1024,
"max_files": 10
}"#;
let config = CacheConfig::new(json).unwrap();
assert_eq!(config.path.windows, "%temp%/TestCache");
assert_eq!(config.path.linux, "/tmp/testcache");
assert_eq!(config.format.filename, "test_{name}.cache");
assert_eq!(config.format.time, "%Y%m%d");
assert_eq!(config.max_size, 1024);
assert_eq!(config.max_files, 10);
}
#[test]
fn test_cache_config_from_partial_json() {
let json = r#"{
"path": {
"linux": "/custom/path"
},
"format": {
"filename": "custom_{name}.cache"
}
}"#;
let config = CacheConfig::new(json).unwrap();
assert_eq!(config.path.linux, "/custom/path");
assert_eq!(config.format.filename, "custom_{name}.cache");
assert!(!config.path.windows.is_empty());
assert!(!config.format.time.is_empty());
}
#[test]
fn test_cache_config_new_or_default() {
let json = "invalid json";
let config = CacheConfig::new_or_default(json);
assert_eq!(config.max_size, 0);
assert_eq!(config.max_files, 0);
}
#[test]
fn test_cache_creation() {
let temp_dir = tempdir().unwrap();
let config_json = format!(
r#"{{
"path": {{
"windows": "{}",
"linux": "{}"
}},
"format": {{
"filename": "{{name}}.cache",
"time": "%Y%m%d"
}},
"max_size": 0,
"max_files": 0
}}"#,
temp_dir.path().to_string_lossy(),
temp_dir.path().to_string_lossy()
);
let config = CacheConfig::new(&config_json).unwrap();
let mut cache = Cache::new(config).unwrap();
let cache_obj = cache.create("test_cache", None).unwrap();
assert_eq!(cache_obj.name(), "test_cache");
cache_obj.write_string("test data").unwrap();
assert!(cache_obj.exists());
let result = cache.create("test_cache", None);
assert!(result.is_err());
if let Err(e) = result {
assert!(matches!(e, CacheError::AlreadyExists(_)));
}
let retrieved = cache.get("test_cache").unwrap();
assert_eq!(retrieved.name(), "test_cache");
assert_eq!(retrieved.id(), cache_obj.id());
let result = cache.get("nonexistent");
assert!(result.is_err());
if let Err(e) = result {
assert!(matches!(e, CacheError::NotFound(_)));
}
assert_eq!(cache.len(), 1);
assert!(!cache.is_empty());
let objects: Vec<_> = cache.iter().collect();
assert_eq!(objects.len(), 1);
assert_eq!(objects[0].name(), "test_cache");
}
#[test]
fn test_cache_operations() {
let temp_dir = tempdir().unwrap();
let config_json = format!(
r#"{{
"path": {{
"windows": "{}",
"linux": "{}"
}},
"format": {{
"filename": "{{name}}.cache",
"time": "%Y%m%d"
}},
"max_size": 0,
"max_files": 0
}}"#,
temp_dir.path().to_string_lossy(),
temp_dir.path().to_string_lossy()
);
let config = CacheConfig::new(&config_json).unwrap();
let mut cache = Cache::new(config).unwrap();
cache.create("cache1", None).unwrap();
cache.create("cache2", None).unwrap();
cache.create("cache3", None).unwrap();
assert_eq!(cache.len(), 3);
cache.remove("cache2").unwrap();
assert_eq!(cache.len(), 2);
assert!(cache.get("cache2").is_err());
cache.clear().unwrap();
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
}
#[test]
fn test_cache_object_operations() {
let temp_dir = tempdir().unwrap();
let config_json = format!(
r#"{{
"path": {{
"windows": "{}",
"linux": "{}"
}},
"format": {{
"filename": "{{name}}.cache",
"time": "%Y%m%d"
}},
"max_size": 0,
"max_files": 0
}}"#,
temp_dir.path().to_string_lossy(),
temp_dir.path().to_string_lossy()
);
let config = CacheConfig::new(&config_json).unwrap();
let mut cache = Cache::new(config).unwrap();
let cache_obj = cache.create("test_operations", None).unwrap();
let test_string = "Hello, World!";
cache_obj.write_string(test_string).unwrap();
let read_string = cache_obj.get_string().unwrap();
assert_eq!(read_string, test_string);
let test_bytes = vec![1, 2, 3, 4, 5];
cache_obj.write_bytes(&test_bytes).unwrap();
let read_bytes = cache_obj.get_bytes().unwrap();
assert_eq!(read_bytes, test_bytes);
let file = cache_obj.get_file().unwrap();
assert!(file.metadata().is_ok());
let size = cache_obj.size().unwrap();
assert!(size > 0);
cache_obj.delete().unwrap();
assert!(!cache_obj.exists());
let new_obj = cache.create("new_cache", None).unwrap();
let created_at = new_obj.created_at();
assert!(created_at.elapsed().is_ok());
}
#[test]
fn test_cache_with_custom_config() {
let temp_dir = tempdir().unwrap();
let base_config_json = format!(
r#"{{
"path": {{
"windows": "{}",
"linux": "{}"
}},
"format": {{
"filename": "base_{{name}}.cache",
"time": "%Y%m%d"
}},
"max_size": 0,
"max_files": 0
}}"#,
temp_dir.path().to_string_lossy(),
temp_dir.path().to_string_lossy()
);
let base_config = CacheConfig::new(&base_config_json).unwrap();
let mut cache = Cache::new(base_config).unwrap();
let custom_config = r#"{
"path": {
"linux": "/custom/path"
},
"format": {
"filename": "custom_{name}_{id}.cache"
}
}"#;
let cache_obj = cache.create("custom_cache", Some(custom_config)).unwrap();
let path_str = cache_obj.path().to_string_lossy().to_string();
cache_obj.write_string("test").unwrap();
assert!(path_str.contains("custom_cache"));
assert!(path_str.contains(".cache"));
}
#[test]
fn test_cache_config_updates() {
let config = CacheConfig::default();
let mut cache = Cache::new(config).unwrap();
let new_config_json = r#"{
"path": {
"windows": "%temp%/UpdatedCache",
"linux": "/tmp/updatedcache"
},
"format": {
"filename": "updated_{name}.cache",
"time": "%H%M%S"
},
"max_size": 2048,
"max_files": 20
}"#;
let new_config = CacheConfig::new(new_config_json).unwrap();
cache.set_config(new_config.clone());
let retrieved_config = cache.get_config();
assert_eq!(retrieved_config.max_size, 2048);
assert_eq!(retrieved_config.max_files, 20);
assert_eq!(retrieved_config.format.filename, "updated_{name}.cache");
}
#[test]
fn test_validate_name() {
assert!(crate::utils::validate_name("valid_name").is_ok());
assert!(crate::utils::validate_name("valid123").is_ok());
assert!(crate::utils::validate_name("a").is_ok());
assert!(crate::utils::validate_name("").is_err());
assert!(crate::utils::validate_name(&"a".repeat(256)).is_err());
assert!(crate::utils::validate_name("test/name").is_err());
assert!(crate::utils::validate_name("test\\name").is_err());
assert!(crate::utils::validate_name("test..name").is_err());
#[cfg(windows)]
{
assert!(crate::utils::validate_name("CON").is_err());
assert!(crate::utils::validate_name("test:name").is_err());
assert!(crate::utils::validate_name("test<name").is_err());
}
}
#[test]
fn test_error_handling() {
let io_error = CacheError::Io(std::io::Error::new(std::io::ErrorKind::NotFound, "test"));
assert_eq!(io_error.kind(), "io");
let generic_error = CacheError::new("Test error");
assert_eq!(generic_error.kind(), "generic");
assert_eq!(generic_error.message(), "Test error");
let io_err: std::io::Error = std::io::Error::new(std::io::ErrorKind::Other, "test");
let cache_err: CacheError = io_err.into();
assert!(cache_err.is_io_error());
let json_err = serde_json::from_str::<CacheConfig>("invalid json");
assert!(json_err.is_err());
}
#[test]
fn test_cache_object_clone() {
let temp_dir = tempdir().unwrap();
let config_json = format!(
r#"{{
"path": {{
"windows": "{}",
"linux": "{}"
}},
"format": {{
"filename": "{{name}}.cache",
"time": "%Y%m%d"
}},
"max_size": 0,
"max_files": 0
}}"#,
temp_dir.path().to_string_lossy(),
temp_dir.path().to_string_lossy()
);
let config = CacheConfig::new(&config_json).unwrap();
let mut cache = Cache::new(config).unwrap();
let cache_obj = cache.create("clone_test", None).unwrap();
cache_obj.write_string("test data").unwrap();
let cloned = cache_obj.clone();
assert_eq!(cloned.name(), cache_obj.name());
assert_eq!(cloned.id(), cache_obj.id());
assert_eq!(cloned.path(), cache_obj.path());
let cloned_content = cloned.get_string().unwrap();
assert_eq!(cloned_content, "test data");
}
#[test]
fn test_expand_path() {
let path_with_tilde = "~/test/path";
let expanded = crate::utils::expand_path(path_with_tilde);
if let Some(home) = dirs::home_dir() {
let home_str = home.to_string_lossy();
assert!(expanded.starts_with(&*home_str));
}
#[cfg(windows)]
{
let path_with_env = "%temp%/test";
let expanded = crate::utils::expand_path(path_with_env);
assert!(!expanded.contains("%temp%"));
}
let unix_path = "path/to/file";
let expanded = crate::utils::expand_path(unix_path);
#[cfg(windows)]
assert!(expanded.contains('\\'));
#[cfg(unix)]
assert!(expanded.contains('/'));
}
#[test]
fn test_cache_clear_with_errors() {
let temp_dir = tempdir().unwrap();
let config_json = format!(
r#"{{
"path": {{
"windows": "{}",
"linux": "{}"
}},
"format": {{
"filename": "{{name}}.cache",
"time": "%Y%m%d"
}},
"max_size": 0,
"max_files": 0
}}"#,
temp_dir.path().to_string_lossy(),
temp_dir.path().to_string_lossy()
);
let config = CacheConfig::new(&config_json).unwrap();
let mut cache = Cache::new(config).unwrap();
let cache_obj = cache.create("test_clear", None).unwrap();
cache_obj.write_string("test data").unwrap();
assert!(cache_obj.exists());
cache.clear().unwrap();
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
assert!(!cache_obj.exists());
}
#[test]
fn test_error_matches() {
let io_error = CacheError::Io(std::io::Error::new(std::io::ErrorKind::NotFound, "test"));
assert!(io_error.is_io_error());
let not_found_error = CacheError::NotFound("test".to_string());
assert!(not_found_error.is_not_found());
let permission_error = CacheError::PermissionDenied("test".to_string());
assert!(permission_error.is_permission_denied());
}
#[test]
fn test_config_serde_roundtrip() {
let config = CacheConfig::default();
let json = serde_json::to_string(&config).unwrap();
let parsed_config = CacheConfig::new(&json).unwrap();
assert_eq!(config.max_size, parsed_config.max_size);
assert_eq!(config.max_files, parsed_config.max_files);
assert_eq!(config.path.windows, parsed_config.path.windows);
assert_eq!(config.path.linux, parsed_config.path.linux);
assert_eq!(config.format.filename, parsed_config.format.filename);
assert_eq!(config.format.time, parsed_config.format.time);
}
}