mod adopt;
use super::{Harness, agent};
use crate::git_tree::{AgentState, GitTree};
use crate::watch::Mark;
use std::path::{Path, PathBuf};
use std::time::Duration;
fn agent_count(model: &super::Rig, ws: &Path) -> usize {
model.tree(ws).map_or(0, |t| t.agents.len())
}
pub(super) fn settle(model: &mut super::Rig, clock: &crate::test_support::FakeClock) {
clock.advance(Duration::from_millis(10));
model.tick(); clock.advance(Duration::from_millis(150));
model.tick(); }
#[test]
fn a_dirty_workspace_root_re_derives_its_snapshot() {
let h = Harness::new();
let (clock, mut model) = h.model();
assert_eq!(agent_count(&model, &h.ws), 1);
h.build_more("c-2", "world"); assert_eq!(agent_count(&model, &h.ws), 1, "stale until re-derived");
model.dirty_handle().mark_all([(h.ws.clone(), Mark::Watch)]);
clock.advance(Duration::from_millis(10));
assert!(!model.tick(), "debounced within the window");
clock.advance(Duration::from_millis(150));
assert!(model.tick(), "window elapsed → snapshot replaced");
assert_eq!(agent_count(&model, &h.ws), 2);
}
#[test]
fn an_unchanged_re_derivation_replaces_nothing() {
let h = Harness::new();
let (clock, mut model) = h.model();
model.dirty_handle().mark_all([(h.ws.clone(), Mark::Watch)]);
clock.advance(Duration::from_millis(10));
model.tick();
clock.advance(Duration::from_millis(150));
assert!(
!model.tick(),
"an unchanged tree suppresses the replacement"
);
}
#[test]
fn a_failed_re_read_keeps_the_last_snapshot() {
let h = Harness::new();
let (clock, mut model) = h.model();
let bogus = PathBuf::from("/nonexistent/workspace");
model
.dirty_handle()
.mark_all([(bogus.clone(), Mark::Watch)]);
settle(&mut model, &clock);
assert!(model.tree(&bogus).is_none(), "a failed read stores nothing");
assert_eq!(agent_count(&model, &h.ws), 1, "the good snapshot is intact");
}
#[test]
fn the_full_sweep_re_derives_every_workspace() {
let h = Harness::new();
let (clock, mut model) = h.model();
h.build_more("c-2", "yo");
clock.advance(Duration::from_secs(15)); model.tick();
clock.advance(Duration::from_millis(150)); model.tick();
assert_eq!(agent_count(&model, &h.ws), 2, "the 15 s sweep re-derived");
}
#[test]
fn the_cheap_sweep_re_probes_and_invalidates_only_live_workspaces() {
let h = Harness::new();
let (clock, mut model) = h.model();
clock.advance(crate::app::dirty::CHEAP_SWEEP);
assert!(!model.tick(), "a quiescent workspace: no targeted re-probe");
model.deriver.trees.insert(
h.ws.clone(),
GitTree {
commits: vec![],
agents: vec![agent("c-1", AgentState::Live)],
},
);
clock.advance(crate::app::dirty::CHEAP_SWEEP);
model.tick(); clock.advance(Duration::from_millis(150));
assert!(model.tick(), "a live agent: the cheap sweep re-derives");
assert_eq!(agent_count(&model, &h.ws), 1, "re-read the real fixture");
}
#[test]
fn an_enumeration_root_event_reconciles_the_workspace_set() {
let mut h = Harness::new();
let (clock, mut model) = h.model();
assert_eq!(model.workspaces().len(), 1);
let ws2 = h.add_workspace("ws2", "d-1");
model
.dirty_handle()
.mark_all([(h.roots.workspaces(), Mark::Watch)]);
model.tick(); assert_eq!(
model.workspaces().len(),
2,
"the new workspace is enumerated"
);
assert_eq!(model.workspace_stats(&ws2), (0, 0, false));
clock.advance(Duration::from_millis(150));
model.tick();
assert_eq!(agent_count(&model, &ws2), 1);
}
#[test]
fn a_vanished_workspace_is_pruned_on_reconcile() {
let mut h = Harness::new();
let ws2 = h.add_workspace("ws2", "d-1");
let (_c, mut model) = h.model();
assert_eq!(
model.workspaces().len(),
2,
"both enumerated at construction"
);
assert_eq!(agent_count(&model, &ws2), 1);
std::fs::remove_file(&ws2).unwrap();
model
.dirty_handle()
.mark_all([(h.roots.workspaces(), Mark::Watch)]);
model.tick();
assert_eq!(model.workspaces().len(), 1, "the removed workspace is gone");
assert!(model.tree(&ws2).is_none(), "its snapshot is pruned");
}
#[test]
fn every_enum_root_and_the_state_root_are_routed() {
let h = Harness::new();
let (_c, mut model) = h.model();
model.dirty_handle().mark_all([
(h.roots.yog_state.clone(), Mark::Watch),
(h.roots.names(), Mark::Watch),
(h.roots.workspaces(), Mark::Watch),
(h.roots.replays(), Mark::Watch),
(h.ws.clone(), Mark::Watch),
]);
assert!(
!model.tick(),
"no snapshot delta this tick (workspace only debounced)"
);
assert_eq!(model.workspaces().len(), 1, "reconcile kept the set");
}