use super::helpers::{make_full_response, make_response};
use crate::stack::crates_io::cache::PersistentCache;
use crate::stack::crates_io::types::{
CacheEntry, CrateData, CrateResponse, PersistentCacheEntry, VersionData,
};
use crate::stack::crates_io::MockCratesIoClient;
use std::time::Duration;
#[test]
fn test_crates_001_cache_entry_creation() {
let entry = CacheEntry::new("test_value", Duration::from_secs(60));
assert_eq!(entry.value, "test_value");
assert!(!entry.is_expired());
}
#[test]
fn test_cache_entry_expiration() {
let entry = CacheEntry::new("value", Duration::from_millis(1));
assert!(!entry.is_expired());
std::thread::sleep(Duration::from_millis(10));
assert!(entry.is_expired());
}
#[test]
fn test_crates_001_cache_entry_zero_ttl() {
let entry = CacheEntry::new(42, Duration::from_secs(0));
assert!(entry.is_expired());
}
#[test]
fn test_crates_001_cache_entry_with_clone() {
let response = make_response("test", "1.0.0");
let entry = CacheEntry::new(response.clone(), Duration::from_secs(60));
assert_eq!(entry.value.krate.name, "test");
}
#[test]
fn test_crates_001_cache_entry_debug() {
let entry = CacheEntry::new("debug_test", Duration::from_secs(60));
let debug = format!("{:?}", entry);
assert!(debug.contains("CacheEntry"));
assert!(debug.contains("debug_test"));
}
#[test]
fn test_mock_client_add_crate() {
let mut mock = MockCratesIoClient::new();
mock.add_crate("trueno", "1.2.0");
let response = mock.get_crate("trueno").unwrap();
assert_eq!(response.krate.name, "trueno");
assert_eq!(response.krate.max_version, "1.2.0");
}
#[test]
fn test_mock_client_not_found() {
let mock = MockCratesIoClient::new();
assert!(mock.get_crate("nonexistent").is_err());
}
#[test]
fn test_mock_client_add_not_found() {
let mut mock = MockCratesIoClient::new();
mock.add_not_found("broken-crate");
assert!(mock.get_crate("broken-crate").is_err());
}
#[test]
fn test_mock_client_get_latest_version() {
let mut mock = MockCratesIoClient::new();
mock.add_crate("aprender", "0.8.1");
let version = mock.get_latest_version("aprender").unwrap();
assert_eq!(version, semver::Version::new(0, 8, 1));
}
#[test]
fn test_mock_client_version_published() {
let mut mock = MockCratesIoClient::new();
mock.add_crate("trueno", "1.2.0");
assert!(mock.is_version_published("trueno", &semver::Version::new(1, 2, 0)).unwrap());
assert!(!mock.is_version_published("trueno", &semver::Version::new(1, 3, 0)).unwrap());
}
#[test]
fn test_mock_client_multiple_crates() {
let mut mock = MockCratesIoClient::new();
mock.add_crate("trueno", "1.2.0")
.add_crate("aprender", "0.8.1")
.add_crate("renacer", "0.6.0")
.add_not_found("nonexistent");
assert!(mock.get_crate("trueno").is_ok());
assert!(mock.get_crate("aprender").is_ok());
assert!(mock.get_crate("renacer").is_ok());
assert!(mock.get_crate("nonexistent").is_err());
}
#[test]
fn test_crate_response_deserialization() {
let json = r#"{
"crate": {
"name": "trueno",
"max_version": "1.2.0",
"max_stable_version": "1.2.0",
"description": "SIMD tensor library",
"downloads": 5000,
"updated_at": "2025-12-01T10:00:00Z"
},
"versions": [
{
"num": "1.2.0",
"yanked": false,
"downloads": 3000,
"created_at": "2025-12-01T10:00:00Z"
},
{
"num": "1.1.0",
"yanked": false,
"downloads": 2000,
"created_at": "2025-11-01T10:00:00Z"
}
]
}"#;
let response: CrateResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.krate.name, "trueno");
assert_eq!(response.krate.max_version, "1.2.0");
assert_eq!(response.versions.len(), 2);
assert!(!response.versions[0].yanked);
}
#[test]
fn test_crates_002_crate_response_clone() {
let mut response = make_full_response("test", "1.0.0", Some("Test crate"), 1000);
response.krate.updated_at = "2025-12-05".to_string();
response.versions[0].created_at = "2025-12-05".to_string();
let cloned = response.clone();
assert_eq!(cloned.krate.name, response.krate.name);
assert_eq!(cloned.versions.len(), response.versions.len());
}
#[test]
fn test_crates_002_crate_data_debug() {
let data = CrateData::new("debug-crate", "2.0.0");
let debug = format!("{:?}", data);
assert!(debug.contains("CrateData"));
assert!(debug.contains("debug-crate"));
}
#[test]
fn test_crates_002_version_data_yanked() {
let version = VersionData { yanked: true, ..VersionData::new("0.1.0", 50) };
assert!(version.yanked);
assert_eq!(version.num, "0.1.0");
}
#[test]
fn test_crates_002_version_data_debug() {
let version = VersionData::new("1.2.3", 999);
let debug = format!("{:?}", version);
assert!(debug.contains("VersionData"));
assert!(debug.contains("1.2.3"));
}
#[test]
fn test_crates_003_mock_client_default() {
let mock = MockCratesIoClient::default();
assert!(mock.get_crate("any").is_err());
}
#[test]
fn test_crates_003_mock_client_debug() {
let mock = MockCratesIoClient::new();
let debug = format!("{:?}", mock);
assert!(debug.contains("MockCratesIoClient"));
}
#[test]
fn test_crates_003_mock_client_chaining() {
let mut mock = MockCratesIoClient::new();
mock.add_crate("a", "1.0.0").add_crate("b", "2.0.0").add_crate("c", "3.0.0");
assert_eq!(mock.get_latest_version("a").unwrap(), semver::Version::new(1, 0, 0));
assert_eq!(mock.get_latest_version("b").unwrap(), semver::Version::new(2, 0, 0));
assert_eq!(mock.get_latest_version("c").unwrap(), semver::Version::new(3, 0, 0));
}
#[test]
fn test_crates_003_version_not_published() {
let mut mock = MockCratesIoClient::new();
mock.add_crate("test", "1.0.0");
let result = mock.is_version_published("test", &semver::Version::new(2, 0, 0));
assert!(!result.unwrap());
}
#[test]
fn test_crates_003_get_latest_version_error() {
let mock = MockCratesIoClient::new();
assert!(mock.get_latest_version("nonexistent").is_err());
}
#[test]
fn test_crates_003_is_version_published_error() {
let mock = MockCratesIoClient::new();
let result = mock.is_version_published("nonexistent", &semver::Version::new(1, 0, 0));
assert!(result.is_err());
}
#[test]
fn test_crates_004_deserialize_null_description() {
let json = r#"{
"crate": {
"name": "minimal",
"max_version": "0.1.0",
"max_stable_version": null,
"description": null,
"downloads": 0,
"updated_at": "2025-01-01T00:00:00Z"
},
"versions": []
}"#;
let response: CrateResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.krate.name, "minimal");
assert!(response.krate.description.is_none());
assert!(response.krate.max_stable_version.is_none());
assert!(response.versions.is_empty());
}
#[test]
fn test_crates_004_deserialize_prerelease() {
let json = r#"{
"crate": {
"name": "beta-crate",
"max_version": "1.0.0-beta.1",
"max_stable_version": "0.9.0",
"description": "Beta software",
"downloads": 100,
"updated_at": "2025-12-01T00:00:00Z"
},
"versions": [
{"num": "1.0.0-beta.1", "yanked": false, "downloads": 50, "created_at": "2025-12-01T00:00:00Z"},
{"num": "0.9.0", "yanked": false, "downloads": 50, "created_at": "2025-11-01T00:00:00Z"}
]
}"#;
let response: CrateResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.krate.max_version, "1.0.0-beta.1");
assert_eq!(response.krate.max_stable_version, Some("0.9.0".to_string()));
}
#[test]
fn test_crates_004_deserialize_yanked_versions() {
let json = r#"{
"crate": {
"name": "yanked-crate",
"max_version": "2.0.0",
"max_stable_version": "2.0.0",
"description": null,
"downloads": 1000,
"updated_at": "2025-12-01T00:00:00Z"
},
"versions": [
{"num": "2.0.0", "yanked": false, "downloads": 500, "created_at": "2025-12-01T00:00:00Z"},
{"num": "1.0.0", "yanked": true, "downloads": 500, "created_at": "2025-01-01T00:00:00Z"}
]
}"#;
let response: CrateResponse = serde_json::from_str(json).unwrap();
assert!(!response.versions[0].yanked);
assert!(response.versions[1].yanked);
}
#[test]
fn test_crates_005_persistent_cache_entry_creation() {
let response = make_response("test", "1.0.0");
let entry = PersistentCacheEntry::new(response, Duration::from_secs(3600));
assert!(!entry.is_expired());
assert_eq!(entry.response.krate.name, "test");
}
#[test]
fn test_crates_005_persistent_cache_entry_expiration() {
let response = make_response("expired", "0.1.0");
let entry = PersistentCacheEntry::new(response, Duration::from_secs(0));
assert!(entry.is_expired());
}
#[test]
fn test_crates_005_persistent_cache_entry_serialization() {
let mut response = make_full_response("serialize", "2.0.0", Some("A test crate"), 1000);
response.versions[0].downloads = 500;
let entry = PersistentCacheEntry::new(response, Duration::from_secs(3600));
let json = serde_json::to_string(&entry).unwrap();
let deserialized: PersistentCacheEntry = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.response.krate.name, "serialize");
assert_eq!(deserialized.response.versions.len(), 1);
}
#[test]
fn test_crates_006_persistent_cache_default() {
let cache = PersistentCache::default();
assert!(cache.entries.is_empty());
}
#[test]
fn test_crates_006_persistent_cache_insert_get() {
let mut cache = PersistentCache::default();
let response = make_response("cached", "1.0.0");
cache.insert("cached".to_string(), response, Duration::from_secs(3600));
let retrieved = cache.get("cached");
assert!(retrieved.is_some());
assert_eq!(retrieved.unwrap().krate.name, "cached");
}
#[test]
fn test_crates_006_persistent_cache_miss() {
let cache = PersistentCache::default();
assert!(cache.get("nonexistent").is_none());
}
#[test]
fn test_crates_006_persistent_cache_expired() {
let mut cache = PersistentCache::default();
let response = make_response("expired", "1.0.0");
cache.insert("expired".to_string(), response, Duration::from_secs(0));
assert!(cache.get("expired").is_none());
}
#[test]
fn test_crates_006_persistent_cache_clear_expired() {
let mut cache = PersistentCache::default();
let valid_response = make_response("valid", "1.0.0");
let expired_response = make_response("expired", "0.1.0");
cache.insert("valid".to_string(), valid_response, Duration::from_secs(3600));
cache.insert("expired".to_string(), expired_response, Duration::from_secs(0));
cache.clear_expired();
assert_eq!(cache.entries.len(), 1);
assert!(cache.get("valid").is_some());
}
#[test]
fn test_crates_006_persistent_cache_serialization() {
let mut cache = PersistentCache::default();
let response = make_response("serialize", "1.0.0");
cache.insert("serialize".to_string(), response, Duration::from_secs(3600));
let json = serde_json::to_string(&cache).unwrap();
let deserialized: PersistentCache = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.entries.len(), 1);
}
#[test]
fn test_crates_013_cache_path_structure() {
let path = PersistentCache::cache_path();
let path_str = path.to_string_lossy();
assert!(path_str.contains("batuta"), "Cache path should contain 'batuta': {}", path_str);
assert!(
path_str.ends_with("crates_io_cache.json"),
"Cache path should end with 'crates_io_cache.json': {}",
path_str
);
}
#[test]
fn test_crates_013_load_returns_default() {
let cache = PersistentCache::load();
let _ = cache.entries.len();
}
#[test]
fn test_crates_013_save_roundtrip() {
let mut cache = PersistentCache::default();
let response = make_response("save-test", "3.0.0");
cache.insert("save-test".to_string(), response, Duration::from_secs(3600));
let result = cache.save();
assert!(result.is_ok(), "save() should succeed: {:?}", result.err());
let loaded = PersistentCache::load();
let entry = loaded.get("save-test");
assert!(entry.is_some(), "Loaded cache should contain 'save-test' entry");
assert_eq!(entry.unwrap().krate.name, "save-test");
}
#[test]
fn test_crates_013_persistent_cache_debug() {
let cache = PersistentCache::default();
let debug = format!("{:?}", cache);
assert!(debug.contains("PersistentCache"));
}
#[test]
fn test_crates_014_load_from_nonexistent() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("nonexistent_cache.json");
let cache = PersistentCache::load_from(&path);
assert!(cache.entries.is_empty());
}
#[test]
fn test_crates_014_load_from_invalid_json() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("bad_cache.json");
std::fs::write(&path, "not valid json {{{").unwrap();
let cache = PersistentCache::load_from(&path);
assert!(cache.entries.is_empty());
}
#[test]
fn test_crates_014_load_from_valid_json() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("good_cache.json");
let mut cache = PersistentCache::default();
let response = make_response("loaded", "2.0.0");
cache.insert("loaded".to_string(), response, Duration::from_secs(3600));
cache.save_to(&path).unwrap();
let loaded = PersistentCache::load_from(&path);
let entry = loaded.get("loaded");
assert!(entry.is_some());
assert_eq!(entry.unwrap().krate.name, "loaded");
}
#[test]
fn test_crates_014_save_to_creates_dirs() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("nested").join("deep").join("cache.json");
let mut cache = PersistentCache::default();
let response = make_response("nested", "1.0.0");
cache.insert("nested".to_string(), response, Duration::from_secs(3600));
let result = cache.save_to(&path);
assert!(result.is_ok());
assert!(path.exists());
}
#[test]
fn test_crates_014_save_load_roundtrip() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("roundtrip.json");
let mut cache = PersistentCache::default();
cache.insert(
"crate-a".to_string(),
make_response("crate-a", "1.0.0"),
Duration::from_secs(3600),
);
cache.insert(
"crate-b".to_string(),
make_response("crate-b", "2.0.0"),
Duration::from_secs(3600),
);
cache.save_to(&path).unwrap();
let loaded = PersistentCache::load_from(&path);
assert_eq!(loaded.entries.len(), 2);
assert!(loaded.get("crate-a").is_some());
assert!(loaded.get("crate-b").is_some());
}
#[test]
fn test_crates_014_load_from_empty_file() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("empty_cache.json");
std::fs::write(&path, "").unwrap();
let cache = PersistentCache::load_from(&path);
assert!(cache.entries.is_empty());
}
#[test]
fn test_crates_014_load_from_wrong_schema() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("wrong_schema.json");
std::fs::write(&path, r#"{"key": "value"}"#).unwrap();
let cache = PersistentCache::load_from(&path);
let _ = cache.entries.len();
}
#[test]
fn test_crates_014_save_empty_cache() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("empty.json");
let cache = PersistentCache::default();
cache.save_to(&path).unwrap();
let loaded = PersistentCache::load_from(&path);
assert!(loaded.entries.is_empty());
}
#[test]
fn test_crates_014_save_to_invalid_path() {
let path = std::path::PathBuf::from("/proc/0/nonexistent/deep/cache.json");
let cache = PersistentCache::default();
let result = cache.save_to(&path);
assert!(result.is_err());
}
#[test]
fn test_crates_014_load_from_directory() {
let dir = tempfile::tempdir().unwrap();
let cache = PersistentCache::load_from(dir.path());
assert!(cache.entries.is_empty());
}