use std::path::Path;
use serde_json::json;
use crate::llm::cache::Cache;
fn write_raw(path: &Path, body: &[u8]) {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).expect("create shard");
}
std::fs::write(path, body).expect("write raw");
}
#[test]
fn put_then_get_round_trips_exact_json_value() {
let temp = tempfile::tempdir().expect("tempdir");
let cache = Cache::new(temp.path().to_path_buf(), 30, 1024 * 1024);
let key = cache.key(
"sys",
"content",
"http://endpoint/v1",
"model",
"openai",
Some(0.2),
);
let value = json!({
"findings": [
{"severity": "high", "line": 42, "message": "bug"},
],
"nested": {"a": [1, 2, 3]},
});
cache.put(&key, &value).expect("put");
let read_back = cache.get(&key);
assert_eq!(
read_back.as_ref(),
Some(&value),
"put/get must round-trip exactly"
);
}
#[test]
fn get_on_absent_key_is_none() {
let temp = tempfile::tempdir().expect("tempdir");
let cache = Cache::new(temp.path().to_path_buf(), 30, 1024 * 1024);
let key = cache.key(
"sys",
"never-written",
"http://endpoint/v1",
"model",
"openai",
Some(0.2),
);
assert!(
cache.get(&key).is_none(),
"an absent key must be a miss, not an error"
);
}
#[test]
fn get_on_corrupt_entry_is_none() {
let temp = tempfile::tempdir().expect("tempdir");
let cache = Cache::new(temp.path().to_path_buf(), 30, 1024 * 1024);
let key = cache.key(
"sys",
"content",
"http://endpoint/v1",
"model",
"openai",
Some(0.2),
);
let path = cache.entry_path(&key);
write_raw(&path, b"this is not JSON {{{");
assert!(
cache.get(&key).is_none(),
"a corrupt entry must be a miss; reads must never fail"
);
}
#[test]
fn get_when_entry_path_cannot_be_read_is_none() {
let temp = tempfile::tempdir().expect("tempdir");
let cache = Cache::new(temp.path().to_path_buf(), 30, 1024 * 1024);
let key = cache.key(
"sys",
"content",
"http://endpoint/v1",
"model",
"openai",
Some(0.2),
);
let value = json!({"x": 1});
cache.put(&key, &value).expect("put");
let path = cache.entry_path(&key);
std::fs::remove_file(&path).expect("remove cache file");
std::fs::create_dir(&path).expect("replace cache file with directory");
let result = cache.get(&key);
assert!(
result.is_none(),
"an entry that cannot be read as bytes must be a miss; reads must never fail"
);
}
#[test]
fn put_creates_the_shard_directory_when_absent() {
let temp = tempfile::tempdir().expect("tempdir");
let cache = Cache::new(temp.path().to_path_buf(), 30, 1024 * 1024);
let key = cache.key(
"sys",
"content",
"http://endpoint/v1",
"model",
"openai",
Some(0.2),
);
let shard = &key.as_hex()[..2];
let shard_path = temp.path().join(shard);
assert!(
!shard_path.exists(),
"fresh cache must not pre-create shards"
);
cache.put(&key, &json!({"x": 1})).expect("put");
assert!(
shard_path.is_dir(),
"put must create the shard directory on demand: {shard_path:?}"
);
}
#[test]
fn two_keys_coexist_on_disk() {
let temp = tempfile::tempdir().expect("tempdir");
let cache = Cache::new(temp.path().to_path_buf(), 30, 1024 * 1024);
let k1 = cache.key(
"sys",
"alpha",
"http://endpoint/v1",
"model",
"openai",
Some(0.2),
);
let k2 = cache.key(
"sys",
"beta",
"http://endpoint/v1",
"model",
"openai",
Some(0.2),
);
cache.put(&k1, &json!({"which": 1})).expect("put k1");
cache.put(&k2, &json!({"which": 2})).expect("put k2");
let v1 = cache.get(&k1);
let v2 = cache.get(&k2);
assert_eq!(v1.as_ref(), Some(&json!({"which": 1})), "k1 round-trips");
assert_eq!(v2.as_ref(), Some(&json!({"which": 2})), "k2 round-trips");
let p1 = cache.entry_path(&k1);
let p2 = cache.entry_path(&k2);
assert!(p1.exists(), "k1 file: {p1:?}");
assert!(p2.exists(), "k2 file: {p2:?}");
assert_ne!(p1, p2, "different keys must land at different paths");
}
#[test]
fn put_over_existing_key_replaces_value() {
let temp = tempfile::tempdir().expect("tempdir");
let cache = Cache::new(temp.path().to_path_buf(), 30, 1024 * 1024);
let key = cache.key(
"sys",
"content",
"http://endpoint/v1",
"model",
"openai",
Some(0.2),
);
cache.put(&key, &json!({"version": 1})).expect("put v1");
cache.put(&key, &json!({"version": 2})).expect("put v2");
let read_back = cache.get(&key);
assert_eq!(
read_back.as_ref(),
Some(&json!({"version": 2})),
"put must overwrite, not append"
);
}
#[cfg(unix)]
#[test]
fn put_replaces_a_symlink_without_writing_through_it() {
use std::os::unix::fs::symlink;
let temp = tempfile::tempdir().expect("tempdir");
let cache = Cache::new(temp.path().join("cache"), 30, 1024 * 1024);
let key = cache.key("sys", "content", "http://e/v1", "m", "openai", None);
let path = cache.entry_path(&key);
std::fs::create_dir_all(path.parent().expect("shard")).expect("create shard");
let outside = temp.path().join("outside.json");
std::fs::write(&outside, b"do not replace me").expect("outside");
symlink(&outside, &path).expect("cache symlink");
cache.put(&key, &json!({"safe": true})).expect("put");
assert_eq!(
std::fs::read(&outside).expect("outside remains"),
b"do not replace me"
);
assert_eq!(cache.get(&key), Some(json!({"safe": true})));
assert!(
!path.is_symlink(),
"the cache entry must replace the symlink"
);
}