//! Tests for writing the next epoch. Do not edit. mod support; use bxcore::{Epoch, LogRecord, SessionId}; use loopd::baseline::Baseline; use loopd::compact; use loopd::memory::{MemorySource, NoMemory}; use loopd::session::Session; use loopd::tools::Registry; use support::Home; struct Block(&'static str); impl MemorySource for Block { fn block(&self) -> Result { Ok(self.0.to_string()) } fn updated(&self, _session: &SessionId, _turn: u64) -> Result<(), String> { Ok(()) } } fn id(s: &str) -> SessionId { SessionId::new(s).unwrap() } fn session(home: &Home) -> (loopd::config::Config, Session) { let cfg = home.config(std::path::Path::new("/tmp/unused.sock")); let baseline = Baseline::assemble(&cfg, &Registry::m2b()).unwrap(); let s = Session::create(&home.dir, id("a"), baseline, cfg.slots.main).unwrap(); (cfg, s) } #[test] fn commit_writes_epoch_end_a_new_baseline_and_a_new_log() { let home = Home::new(); let (cfg, mut s) = session(&home); s.append(LogRecord::User { time: bxcore::Timestamp::parse("2026-09-18T08:00:00.000Z").unwrap(), content: "hi".to_string(), speaker: None, }) .unwrap(); compact::commit( &mut s, &cfg, &Registry::m2b(), &NoMemory, "the sum".to_string(), ) .unwrap(); assert_eq!(s.epoch(), Epoch(1)); assert_eq!(s.carry(), Some("the sum")); assert_eq!(s.slot(), cfg.slots.main); assert_eq!(s.turns(), 1, "the old user still counts"); assert_eq!( s.records() .iter() .filter(|r| matches!(r, LogRecord::User { .. })) .count(), 0 ); let old = home.records("a"); assert!( matches!(old.last(), Some(LogRecord::EpochEnd { next, summary, .. }) if *next == Epoch(1) && summary == "the sum") ); assert!(home.dir.join("sessions/a/1.baseline.json").is_file()); let new_log = home.read("sessions/a/1.jsonl"); assert!(new_log.contains("\"epoch\":1"), "{new_log}"); } #[test] fn the_new_baseline_takes_a_fresh_memory_block() { let home = Home::new(); let (cfg, mut s) = session(&home); compact::commit( &mut s, &cfg, &Registry::m2b(), &Block("I take my tea strong."), "sum".to_string(), ) .unwrap(); assert!( s.baseline().system.contains("I take my tea strong."), "{}", s.baseline().system ); let on_disk = home.read("sessions/a/1.baseline.json"); assert!(on_disk.contains("I take my tea strong."), "{on_disk}"); } #[test] fn reopen_loads_the_new_epoch() { let home = Home::new(); let (cfg, mut s) = session(&home); compact::commit( &mut s, &cfg, &Registry::m2b(), &NoMemory, "carry".to_string(), ) .unwrap(); drop(s); let s = Session::open(&home.dir, id("a")).unwrap(); assert_eq!(s.epoch(), Epoch(1)); assert_eq!(s.carry(), Some("carry")); } #[test] fn a_second_commit_does_not_write_another_epoch_end_on_the_old_log() { let home = Home::new(); let (cfg, mut s) = session(&home); compact::commit( &mut s, &cfg, &Registry::m2b(), &NoMemory, "first".to_string(), ) .unwrap(); let ends = home .records("a") .iter() .filter(|r| matches!(r, LogRecord::EpochEnd { .. })) .count(); assert_eq!(ends, 1); compact::commit( &mut s, &cfg, &Registry::m2b(), &NoMemory, "second".to_string(), ) .unwrap(); assert_eq!(s.epoch(), Epoch(2)); let ends = home .records("a") .iter() .filter(|r| matches!(r, LogRecord::EpochEnd { .. })) .count(); assert_eq!(ends, 1, "epoch 0 still has one EpochEnd"); let mid = home.read("sessions/a/1.jsonl"); assert!(mid.contains("\"type\":\"epoch_end\""), "{mid}"); } #[test] fn resume_after_epoch_end_without_next_files_does_not_write_a_second_end() { let home = Home::new(); let (cfg, mut s) = session(&home); s.append(LogRecord::EpochEnd { time: bxcore::Timestamp::now(), next: Epoch(1), summary: "already".to_string(), }) .unwrap(); drop(s); let mut s = Session::open(&home.dir, id("a")).unwrap(); assert_eq!(s.epoch(), Epoch(0)); compact::commit( &mut s, &cfg, &Registry::m2b(), &NoMemory, "ignored".to_string(), ) .unwrap(); let ends = home .records("a") .iter() .filter(|r| matches!(r, LogRecord::EpochEnd { .. })) .count(); assert_eq!(ends, 1); assert_eq!(s.epoch(), Epoch(1)); assert_eq!( s.carry(), Some("already"), "the summary already in EpochEnd is the carry" ); } #[test] fn the_new_session_start_keeps_the_live_slot() { let home = Home::new(); let cfg = home.config(std::path::Path::new("/tmp/unused.sock")); let baseline = Baseline::assemble(&cfg, &Registry::m2b()).unwrap(); let mut s = Session::create(&home.dir, id("a"), baseline, 7).unwrap(); compact::commit(&mut s, &cfg, &Registry::m2b(), &NoMemory, "s".to_string()).unwrap(); assert_eq!(s.slot(), 7); match &s.records()[0] { LogRecord::SessionStart { slot, epoch, .. } => { assert_eq!(*slot, 7); assert_eq!(*epoch, Epoch(1)); } other => panic!("{other:?}"), } }