//! 会话树与 JSONL 持久化的单元测试。 //! Unit tests for session trees and JSONL persistence. use focus_core::model::Message; use focus_harness::session::{SessionEntry, SessionStore, SessionTree}; use std::path::PathBuf; fn temp_dir(tag: &str) -> PathBuf { let d = std::env::temp_dir().join(format!( "focus-harness-session-{}-{}", tag, std::process::id() )); let _ = std::fs::remove_dir_all(&d); std::fs::create_dir_all(&d).unwrap(); d } #[test] fn appends_and_loads_entries() { let dir = temp_dir("append"); let store = SessionStore::new(&dir); store .append( "s1", &SessionEntry::new("e1".into(), None, Message::user_text("hi")), ) .unwrap(); store .append( "s1", &SessionEntry::new("e2".into(), Some("e1".into()), Message::user_text("there")), ) .unwrap(); let entries = store.load("s1").unwrap(); assert_eq!(entries.len(), 2); assert_eq!(entries[0].id, "e1"); assert_eq!(entries[0].parent_id, None); assert_eq!(entries[1].id, "e2"); assert_eq!(entries[1].parent_id.as_deref(), Some("e1")); assert!(matches!(entries[0].message, Message::User(_))); } #[test] fn next_entry_id_is_incremental() { let dir = temp_dir("seq"); let store = SessionStore::new(&dir); assert_eq!(store.next_entry_id("s1").unwrap(), "e1"); store .append( "s1", &SessionEntry::new("e1".into(), None, Message::user_text("a")), ) .unwrap(); assert_eq!(store.next_entry_id("s1").unwrap(), "e2"); store .append( "s1", &SessionEntry::new("e3".into(), Some("e1".into()), Message::user_text("b")), ) .unwrap(); // e3 已存在 → 下一个是 e4。 // e3 exists → the next is e4. assert_eq!(store.next_entry_id("s1").unwrap(), "e4"); } #[test] fn lists_sessions() { let dir = temp_dir("list"); let store = SessionStore::new(&dir); store .append( "alpha", &SessionEntry::new("e1".into(), None, Message::user_text("a")), ) .unwrap(); store .append( "beta", &SessionEntry::new("e1".into(), None, Message::user_text("b")), ) .unwrap(); let ids = store.list_sessions().unwrap(); assert_eq!(ids, vec!["alpha".to_string(), "beta".to_string()]); } fn tree() -> SessionTree { let entries = vec![ SessionEntry::new("e1".into(), None, Message::user_text("root")), SessionEntry::new( "e2".into(), Some("e1".into()), Message::user_text("child A"), ), SessionEntry::new( "e3".into(), Some("e1".into()), Message::user_text("child B"), ), SessionEntry::new( "e4".into(), Some("e3".into()), Message::user_text("grandchild"), ), ]; SessionTree::build(entries) } #[test] fn tree_queries() { let t = tree(); assert_eq!(t.roots().len(), 1); assert_eq!(t.roots()[0].id, "e1"); let children = t.children_of("e1"); assert_eq!(children.len(), 2); let path = t.branch("e4"); let ids: Vec<&str> = path.iter().map(|e| e.id.as_str()).collect(); assert_eq!(ids, vec!["e1", "e3", "e4"]); let leaves = t.leaf_ids(); assert_eq!(leaves.len(), 2); assert!(leaves.contains(&"e2")); assert!(leaves.contains(&"e4")); }