//! Tests for the session store. Do not edit. mod support; use loopd::baseline::Baseline; use loopd::session::{Session, SessionError}; use proto::{CallId, DataClass, Epoch, LogRecord, SessionId, Timestamp}; use support::Home; fn baseline() -> Baseline { Baseline { system: "sys".to_string(), tools: vec![], } } fn id(s: &str) -> SessionId { SessionId::new(s).unwrap() } fn ts() -> Timestamp { Timestamp::parse("2026-09-18T08:00:00.000Z").unwrap() } #[test] fn create_writes_the_baseline_file_and_the_start_record() { let home = Home::new(); let s = Session::create(&home.dir, id("a"), baseline(), 3).unwrap(); assert_eq!(s.dir(), home.dir.join("sessions").join("a")); assert_eq!( Baseline::from_json(&home.read("sessions/a/0.baseline.json")).unwrap(), baseline() ); let records = home.records("a"); assert_eq!(records.len(), 1); match &records[0] { LogRecord::SessionStart { session, epoch, slot, baseline: hash, .. } => { assert_eq!(session, &id("a")); assert_eq!(*epoch, Epoch(0)); assert_eq!(*slot, 3); assert_eq!(*hash, baseline().hash().unwrap()); } other => panic!("{other:?}"), } assert_eq!(s.records(), &records[..]); assert!(matches!( Session::create(&home.dir, id("a"), baseline(), 0), Err(SessionError::Exists(_)) )); } #[test] fn append_is_visible_on_disk_at_once_and_after_reopen() { let home = Home::new(); let mut s = Session::create(&home.dir, id("a"), baseline(), 0).unwrap(); s.append(LogRecord::User { time: ts(), content: "hi\nthere \"quoted\" caf\u{e9}".to_string(), }) .unwrap(); assert_eq!( home.records("a").len(), 2, "written and synced before append returns" ); drop(s); let s = Session::open(&home.dir, id("a")).unwrap(); assert_eq!(s.records().len(), 2); assert_eq!(s.baseline(), &baseline()); assert!( matches!(s.records()[1], LogRecord::User { ref content, .. } if content.contains("caf\u{e9}")) ); } #[test] fn resume_uses_the_baseline_file_not_system_md() { let home = Home::new(); let s = Session::create(&home.dir, id("a"), baseline(), 0).unwrap(); drop(s); home.write("system.md", "A different prompt.\n"); let s = Session::open(&home.dir, id("a")).unwrap(); assert_eq!(s.baseline().system, "sys"); } #[test] fn opening_a_missing_session_is_not_found() { let home = Home::new(); assert!(matches!( Session::open(&home.dir, id("nope")), Err(SessionError::NotFound(_)) )); } #[test] fn a_torn_log_is_refused_with_the_line_number() { let home = Home::new(); let mut s = Session::create(&home.dir, id("a"), baseline(), 0).unwrap(); s.append(LogRecord::User { time: ts(), content: "hi".to_string(), }) .unwrap(); drop(s); let path = "sessions/a/0.jsonl"; let good = home.read(path); // A last line cut in the middle. home.write(path, &good[..good.len() - 5]); match Session::open(&home.dir, id("a")).map(|_| ()) { Err(SessionError::Torn { line, .. }) => assert_eq!(line, 2), other => panic!("{other:?}"), } // A complete line that is not a record. home.write(path, &format!("{good}{{\"type\":\"zz\"}}\n")); match Session::open(&home.dir, id("a")).map(|_| ()) { Err(SessionError::Torn { line, path, .. }) => { assert_eq!(line, 3); assert!(path.ends_with("0.jsonl")); } other => panic!("{other:?}"), } let e: Box = Box::new(Session::open(&home.dir, id("a")).map(|_| ()).unwrap_err()); assert!(e.to_string().contains("0.jsonl:3"), "{e}"); } #[test] fn call_ids_continue_across_a_reopen() { let home = Home::new(); let mut s = Session::create(&home.dir, id("a"), baseline(), 0).unwrap(); assert_eq!(s.next_call(), CallId(1)); assert_eq!(s.next_call(), CallId(2)); s.append(LogRecord::ToolResult { time: ts(), call: CallId(2), tool_call_id: "x".to_string(), content: String::new(), class: DataClass::Public, untrusted: false, truncated: false, }) .unwrap(); drop(s); let mut s = Session::open(&home.dir, id("a")).unwrap(); assert_eq!( s.next_call(), CallId(3), "one more than the highest call in the log" ); let mut fresh = Session::create(&home.dir, id("b"), baseline(), 0).unwrap(); assert_eq!(fresh.next_call(), CallId(1)); } #[test] fn last_usage_is_the_latest_usage_record() { let home = Home::new(); let mut s = Session::create(&home.dir, id("a"), baseline(), 0).unwrap(); assert_eq!(s.last_usage(), None); s.append(LogRecord::Usage { time: ts(), cache_n: 1, prompt_n: 2, predicted_n: 3, reasoning_tokens: 0, thinking_capped: false, }) .unwrap(); s.append(LogRecord::User { time: ts(), content: "x".to_string(), }) .unwrap(); s.append(LogRecord::Usage { time: ts(), cache_n: 6, prompt_n: 7, predicted_n: 8, reasoning_tokens: 4, thinking_capped: true, }) .unwrap(); let u = s.last_usage().unwrap(); assert_eq!( ( u.cache_n, u.prompt_n, u.predicted_n, u.reasoning_tokens, u.thinking_capped ), (6, 7, 8, 4, true) ); }