use super::cache::{GOALS_CACHE_FILE, GoalCache, SM_STATE_SUBDIR};
use super::error::SmGoalError;
use super::model::{Goal, GoalStatus};
use chrono::{TimeZone, Utc};
use tempfile::TempDir;
fn ts() -> chrono::DateTime<Utc> {
Utc.with_ymd_and_hms(2026, 6, 16, 12, 0, 0).unwrap()
}
fn sample_goals() -> Vec<Goal> {
let mut a = Goal::new("g-aaaa1111", "first goal", vec!["t1".into()], ts());
a.status = GoalStatus::InProgress;
let b = Goal::new("g-bbbb2222", "second goal", vec![], ts());
vec![a, b]
}
#[test]
fn cache_path_is_under_sm_subdir() {
let dir = TempDir::new().expect("tempdir");
let cache = GoalCache::new(dir.path());
assert!(cache.dir().ends_with(SM_STATE_SUBDIR));
assert!(
cache
.path()
.ends_with(format!("{SM_STATE_SUBDIR}/{GOALS_CACHE_FILE}"))
);
}
#[test]
fn save_then_load_round_trips() {
let dir = TempDir::new().expect("tempdir");
let cache = GoalCache::new(dir.path());
let goals = sample_goals();
cache.save(&goals).expect("save");
let back = cache.load().expect("load");
assert_eq!(goals, back);
}
#[test]
fn load_missing_is_empty() {
let dir = TempDir::new().expect("tempdir");
let cache = GoalCache::new(dir.path());
assert!(cache.load().expect("load").is_empty());
}
#[test]
fn save_creates_sm_subdir() {
let dir = TempDir::new().expect("tempdir");
let cache = GoalCache::new(dir.path());
assert!(!cache.dir().exists(), "no I/O before save");
cache.save(&sample_goals()).expect("save");
assert!(cache.path().exists(), "save must create the cache file");
}
#[test]
fn save_is_atomic_overwrite() {
let dir = TempDir::new().expect("tempdir");
let cache = GoalCache::new(dir.path());
cache.save(&sample_goals()).expect("first save");
let smaller = vec![Goal::new("g-cccc3333", "only goal", vec![], ts())];
cache.save(&smaller).expect("second save");
let back = cache.load().expect("load");
assert_eq!(back, smaller, "second save must fully replace the first");
let entries: Vec<_> = std::fs::read_dir(cache.dir())
.expect("read dir")
.filter_map(Result::ok)
.map(|e| e.file_name().to_string_lossy().into_owned())
.collect();
assert_eq!(
entries,
vec![GOALS_CACHE_FILE.to_string()],
"only the cache file must remain — no leaked temp files; got {entries:?}"
);
}
#[test]
fn load_rejects_malformed_file() {
let dir = TempDir::new().expect("tempdir");
let cache = GoalCache::new(dir.path());
std::fs::create_dir_all(cache.dir()).expect("mkdir");
std::fs::write(cache.path(), b"{ this is not valid json").expect("write garbage");
match cache.load() {
Err(SmGoalError::Serde { .. }) => {}
other => panic!("malformed cache must be a Serde error, got {other:?}"),
}
}