mod lineage;
use super::*;
use crate::fixture::roster;
use tempfile::TempDir;
fn laid(state: &str, origin: i64) -> (TempDir, Places, Vec<PathBuf>) {
let tmp = TempDir::new().expect("tmp");
let recipe = roster::resolve(state).expect("state");
let hold = lay(tmp.path(), recipe, origin).expect("lay");
let places = Places::under(tmp.path());
(tmp, places, hold)
}
#[test]
fn a_laid_state_enumerates_as_a_named_workspace_with_its_conversations() {
let (_tmp, places, _) = laid("busy", 2_000_000_000);
let found = crate::binding::workspaces(&places.data, &places.litany);
assert_eq!(found.len(), 1, "one workspace");
let ws = &found[0];
assert_eq!(
ws.kind,
crate::binding::WorkspaceKind::Named {
name: roster::WORKSPACE.to_owned()
}
);
let tree = crate::git_tree::GitTree::from_repo(&ws.path).expect("derive");
let recipe = roster::resolve("busy").expect("busy");
assert_eq!(tree.agents.len(), recipe.workspaces[0].convs.len());
}
#[test]
fn the_wound_state_lays_a_spoken_wound_and_a_mute_one() {
let now = i64::try_from(
std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs(),
)
.unwrap();
let (_tmp, places, _) = laid("wound", now);
let ws = places.workspace(roster::WORKSPACE);
let grace = crate::app::Cadence::default().wound_grace();
let read = |id: &str| {
crate::steps_view::build(&ws, id, crate::git_tree::AgentState::Stopped, now, grace)
};
let (spoke, mute) = (read("c-101"), read("c-102"));
assert!(matches!(
crate::steps_view::latest_wound(&spoke),
crate::steps_view::Wound::Spoke(_)
));
assert!(matches!(
crate::steps_view::latest_wound(&mute),
crate::steps_view::Wound::Mute
));
assert!(!spoke.orphan.orphaned());
assert!(!mute.orphan.orphaned());
}
#[test]
fn the_orphan_state_lays_both_tails() {
let (_tmp, places, _) = laid("orphan", 2_000_000_000);
let ws = places.workspace(roster::WORKSPACE);
let mail = crate::steps_view::build_aged(&ws, "c-201", crate::git_tree::AgentState::Stopped);
let window = crate::steps_view::build_aged(&ws, "c-202", crate::git_tree::AgentState::Stopped);
assert!(
mail.orphan
.banner()
.contains(crate::steps_view::ORPHANED_MAIL)
);
assert!(
window
.orphan
.banner()
.contains(crate::steps_view::ORPHANED_WINDOW)
);
assert!(mail.orphan.banner().contains("driver.log"));
}
#[test]
fn every_client_leaf_is_registered_in_every_workspace() {
let (_tmp, places, _) = laid("busy", 2_000_000_000);
for role in LEAVES {
if role == Role::Server {
continue;
}
let client = registry::Client::parse(&role.common_name()).expect("name");
let seen = registry::registered(&places.state, &client);
assert!(seen.contains(roster::WORKSPACE), "{}", role.leaf());
}
}
#[test]
fn the_settings_state_lays_a_cadence_and_a_wall() {
let (_tmp, places, _) = laid("settings", 2_000_000_000);
let cadence = places.state.join(crate::app::cadence::CADENCE_YAML);
assert!(cadence.is_file(), "{}", cadence.display());
let wall = places
.walls
.join(roster::WORKSPACE)
.join("brazen")
.join("config.toml");
assert!(wall.is_file(), "{}", wall.display());
let (_bare, bare, _) = laid("busy", 2_000_000_000);
assert!(!bare.state.join(crate::app::cadence::CADENCE_YAML).exists());
assert!(!bare.walls.exists());
}
#[test]
fn the_empty_state_is_seeded_and_has_no_workspace() {
let (_tmp, places, hold) = laid("empty", 2_000_000_000);
assert!(places.litany.join("models.yaml").is_file());
assert!(crate::binding::workspaces(&places.data, &places.litany).is_empty());
assert!(hold.is_empty());
}
#[test]
fn a_streaming_step_asks_for_the_two_fds_that_make_it_live() {
let (_tmp, _places, hold) = laid("busy", 2_000_000_000);
assert_eq!(hold.len(), 2, "one inbox dir and one response file");
assert!(hold[0].is_dir(), "the executor lock is a directory");
assert!(hold[1].is_file(), "the writer fd is the response file");
assert!(laid("settings", 2_000_000_000).2.is_empty());
}
#[test]
fn two_lays_of_one_state_are_the_same_tree() {
let (_a, first, _) = laid("transcript", 2_000_000_000);
let (_b, second, _) = laid("transcript", 2_000_000_000);
let (_c, later, _) = laid("transcript", 2_000_000_777);
let read = |p: &Places, tail: &str| {
std::fs::read(p.workspace(roster::WORKSPACE).join(tail)).expect("read")
};
for tail in ["agents/c-301/goal.md", "steps/c-301/000/response.json"] {
assert_eq!(read(&first, tail), read(&second, tail), "{tail}");
}
let trunk = |p: &Places| {
let repo = p.workspace(roster::WORKSPACE).join("repo.git");
std::fs::read(repo.join("refs/heads/config/default")).expect("trunk ref")
};
assert_eq!(trunk(&first), trunk(&second));
assert_eq!(trunk(&first), trunk(&later), "the trunk date is fixed");
}
#[test]
fn a_conversations_last_action_is_the_recipes_offset() {
let origin = 2_000_000_000;
let (_tmp, places, _) = laid("busy", origin);
let ws = places.workspace(roster::WORKSPACE);
let tree = crate::git_tree::GitTree::from_repo(&ws).expect("derive");
let recipe = roster::resolve("busy").expect("busy");
for conv in recipe.workspaces[0].convs {
let agent = tree
.agents
.iter()
.find(|a| a.agent_id == conv.id)
.unwrap_or_else(|| panic!("{}", conv.id));
assert_eq!(
agent.last_action_unix,
origin - conv.age_secs,
"{}",
conv.id
);
}
}
#[test]
fn a_settled_step_carries_the_meta_a_returned_call_writes() {
assert!(meta(0).contains("1970-01-01"));
let (_tmp, places, _) = laid("settings", 2_000_000_000);
let path = places
.workspace(roster::WORKSPACE)
.join("steps/c-401/000/meta.json");
assert!(path.is_file());
}
#[test]
fn a_lay_into_an_unmakeable_root_refuses() {
let tmp = TempDir::new().expect("tmp");
let file = tmp.path().join("f");
std::fs::write(&file, "x").expect("write");
let recipe = roster::resolve("busy").expect("busy");
assert!(lay(&file.join("under"), recipe, 2_000_000_000).is_err());
}
#[test]
fn an_unwritable_registry_refuses_the_lay() {
let tmp = TempDir::new().expect("tmp");
let places = Places::under(tmp.path());
mkdir(&places.state).expect("state");
std::fs::write(places.state.join(registry::CLIENTS), "not a dir").expect("write");
let recipe = roster::resolve("busy").expect("busy");
assert!(
lay(tmp.path(), recipe, 2_000_000_000)
.expect_err("refuse")
.contains("register")
);
}