//! Tests for the baseline and the replay function. Do not edit. mod support; use loopd::baseline::{Baseline, messages}; use loopd::llama::ChatMessage; use loopd::tools::Registry; use proto::{CallId, DataClass, Epoch, Hash32, LogRecord, SessionId, Timestamp, ToolCall}; use std::path::Path; use support::Home; fn ts() -> Timestamp { Timestamp::parse("2026-09-18T08:00:00.000Z").unwrap() } #[test] fn assembles_system_prompt_and_core_schemas() { let home = Home::new(); let cfg = home.config(Path::new("/tmp/unused.sock")); let b = Baseline::assemble(&cfg, &Registry::m2b()).unwrap(); assert_eq!( b.system, "You are Boxmaker, a test agent.", "trailing newline trimmed" ); let names: Vec<&str> = b.tools.iter().map(|t| t.name.as_str()).collect(); assert_eq!( names, ["clock", "find_tool", "call_tool"], "core tools, then the two meta-tools; not echo" ); } #[test] fn core_memory_is_appended_when_present_and_only_then() { let home = Home::new(); let cfg = home.config(Path::new("/tmp/unused.sock")); home.write("memory/core.md", "The owner likes cork.\n\n"); let b = Baseline::assemble(&cfg, &Registry::m2b()).unwrap(); assert_eq!( b.system, "You are Boxmaker, a test agent.\n\nThe owner likes cork." ); home.write("memory/core.md", " \n"); let b = Baseline::assemble(&cfg, &Registry::m2b()).unwrap(); assert_eq!( b.system, "You are Boxmaker, a test agent.", "an empty core file adds nothing" ); } #[test] fn a_missing_system_prompt_is_an_error_naming_the_file() { let home = Home::new(); let mut cfg = home.config(Path::new("/tmp/unused.sock")); cfg.baseline.system = home.dir.join("nope.md"); let e = Baseline::assemble(&cfg, &Registry::m2b()).unwrap_err(); assert!(e.to_string().contains("nope.md"), "{e}"); } #[test] fn json_round_trip_and_hash() { let home = Home::new(); let cfg = home.config(Path::new("/tmp/unused.sock")); let b = Baseline::assemble(&cfg, &Registry::m2b()).unwrap(); let text = b.to_json().unwrap(); assert_eq!(Baseline::from_json(&text).unwrap(), b); assert_eq!( b.hash().unwrap(), proto::sha256(text.as_bytes()).unwrap(), "the hash is of the JSON as written" ); assert_ne!(b.hash().unwrap(), Hash32::ZERO); let mut other = b.clone(); other.system.push('!'); assert_ne!(other.hash().unwrap(), b.hash().unwrap()); assert!( Baseline::from_json(&text.replacen("\"system\"", "\"zz\":1,\"system\"", 1)).is_err(), "unknown keys are rejected" ); } #[test] fn replay_keeps_only_messages_in_order_and_unchanged() { let b = Baseline { system: "sys".to_string(), tools: vec![], }; let call = ToolCall { id: "c1".to_string(), name: "clock".to_string(), arguments: "{}".to_string(), }; let records = vec![ LogRecord::SessionStart { time: ts(), session: SessionId::new("s").unwrap(), epoch: Epoch(0), slot: 0, baseline: Hash32::ZERO, }, LogRecord::User { time: ts(), content: "hi".to_string(), }, LogRecord::Assistant { time: ts(), content: None, reasoning_content: Some("think".to_string()), tool_calls: vec![call.clone()], }, LogRecord::Usage { time: ts(), cache_n: 1, prompt_n: 2, predicted_n: 3, reasoning_tokens: 1, thinking_capped: false, }, LogRecord::ToolResult { time: ts(), call: CallId(1), tool_call_id: "c1".to_string(), content: "noon".to_string(), class: DataClass::Public, untrusted: false, truncated: false, }, LogRecord::CacheLoss { time: ts(), expected: 10, got: 0, }, LogRecord::Assistant { time: ts(), content: Some("It is noon.".to_string()), reasoning_content: None, tool_calls: vec![], }, LogRecord::EpochEnd { time: ts(), next: Epoch(1), summary: "x".to_string(), }, ]; let want = vec![ ChatMessage::System { content: "sys".to_string(), }, ChatMessage::User { content: "hi".to_string(), }, ChatMessage::Assistant { content: None, reasoning_content: Some("think".to_string()), tool_calls: vec![call], }, ChatMessage::Tool { tool_call_id: "c1".to_string(), content: "noon".to_string(), }, ChatMessage::Assistant { content: Some("It is noon.".to_string()), reasoning_content: None, tool_calls: vec![], }, ]; assert_eq!(messages(&b, &records), want); assert_eq!( messages(&b, &[]), vec![ChatMessage::System { content: "sys".to_string() }] ); } /// Replaying a prefix of the log gives a prefix of the messages: the function never reorders /// or rewrites. Checked for every prefix of a generated log. #[test] fn replay_of_a_prefix_is_a_prefix() { let b = Baseline { system: "sys".to_string(), tools: vec![], }; let mut records = Vec::new(); let mut seed: u64 = 0x9e3779b97f4a7c15; let mut next = || { seed ^= seed << 13; seed ^= seed >> 7; seed ^= seed << 17; seed }; for i in 0..200u64 { let r = match next() % 5 { 0 => LogRecord::User { time: ts(), content: format!("u{i}"), }, 1 => LogRecord::Assistant { time: ts(), content: Some(format!("a{i}")), reasoning_content: None, tool_calls: vec![], }, 2 => LogRecord::ToolResult { time: ts(), call: CallId(i), tool_call_id: format!("t{i}"), content: "x".repeat((next() % 50) as usize), class: DataClass::Private, untrusted: true, truncated: false, }, 3 => LogRecord::Usage { time: ts(), cache_n: i, prompt_n: 1, predicted_n: 1, reasoning_tokens: 0, thinking_capped: false, }, _ => LogRecord::CacheLoss { time: ts(), expected: i, got: 0, }, }; records.push(r); } let whole = messages(&b, &records); for n in 0..=records.len() { let part = messages(&b, &records[..n]); assert_eq!(whole[..part.len()], part[..], "prefix of {n} records"); } }