use super::*;
use std::path::PathBuf;
use tempfile::{TempDir, tempdir};
fn ambient() -> Env {
Env::from_pairs([
("HOME", "/h"),
("XDG_DATA_HOME", "/d"),
("XDG_STATE_HOME", "/s"),
])
}
fn world_at(dir: &TempDir) -> Env {
crate::world::compose(&Env::from_pairs([
("HOME", dir.path().to_string_lossy().into_owned()),
(
"XDG_DATA_HOME",
dir.path().join("data").to_string_lossy().into_owned(),
),
(
"XDG_STATE_HOME",
dir.path().join("state").to_string_lossy().into_owned(),
),
]))
}
#[test]
fn no_marks_var_resolves_the_world_space_and_keeps_ss16_2_state_where_it_is() {
let world = crate::world::compose(&ambient());
let space = space(&world);
assert_eq!(space.state, PathBuf::from("/d/yog/world/state"));
assert_eq!(space.config, PathBuf::from("/d/yog/world/config"));
assert_eq!(
config_file(&space.config),
PathBuf::from("/d/yog/world/config/balls/config.toml")
);
}
#[test]
fn an_unset_state_home_still_folds_balls_own_default() {
let space = space(&Env::from_pairs([("HOME", "/h"), ("XDG_DATA_HOME", "/d")]));
assert_eq!(space.state, PathBuf::from("/h/.local/state"));
}
#[test]
fn the_marks_var_resolves_one_root_serving_as_both_homes() {
let world = crate::world::compose(&ambient()).with_overrides(&[(YOG_MARKS, "/agent/space")]);
let space = space(&world);
assert_eq!(space.state, PathBuf::from("/agent/space"));
assert_eq!(space.config, PathBuf::from("/agent/space"));
}
#[test]
fn an_agents_own_root_is_its_wall_plus_marks_and_the_pairs_ride_the_wall_seam() {
let world = crate::world::compose(&ambient());
let ws = Path::new("/d/yog/workspaces/corp");
assert_eq!(
own_root(&world, ws),
PathBuf::from("/d/yog/world/walls/corp/marks")
);
assert_eq!(
pairs(&world, ws, true),
vec![(
YOG_MARKS.to_owned(),
"/d/yog/world/walls/corp/marks".to_owned()
)]
);
assert!(pairs(&world, ws, false).is_empty());
}
#[test]
fn a_space_with_nothing_written_reads_as_balls_own_default() {
let dir = tempdir().unwrap();
let world = world_at(&dir);
let ws = dir.path().join("workspaces").join("home");
assert_eq!(read(&world, &ws).branch(), SHARED_BRANCH);
}
#[test]
fn a_write_lands_in_the_spaces_own_balls_config_and_reads_back() {
let dir = tempdir().unwrap();
let world = world_at(&dir);
let ws = dir.path().join("workspaces").join("home");
let state = dir.path().join("ops");
let landed = apply(
&read(&world, &ws),
&state,
"T0",
"balls/agents/home",
crate::registry::Client::default(),
)
.unwrap();
assert_eq!(landed, "balls/agents/home");
let space = read(&world, &ws);
let file = config_file(&space.config);
assert_eq!(
std::fs::read_to_string(&file).unwrap(),
"tasks_branch = \"balls/agents/home\"\n"
);
assert!(file.starts_with(dir.path().join("data/yog/world/walls/home/marks")));
assert_eq!(space.branch(), "balls/agents/home");
assert_eq!(
apply(
&read(&world, &ws),
&state,
"T1",
SHARED_BRANCH,
crate::registry::Client::default()
)
.unwrap(),
SHARED_BRANCH
);
assert_eq!(read(&world, &ws).branch(), SHARED_BRANCH);
}
#[test]
fn every_write_leaves_one_ops_row_naming_the_file_and_the_branch() {
let dir = tempdir().unwrap();
let world = world_at(&dir);
let ws = dir.path().join("workspaces").join("home");
let state = dir.path().join("ops");
apply(
&read(&world, &ws),
&state,
"T0",
"balls/mine",
crate::registry::Client::default(),
)
.unwrap();
let rows = crate::opslog::tail(&state, 10);
assert_eq!(rows.len(), 1);
assert_eq!(
rows[0].argv,
vec!["yog-step", "marks", "balls/mine"]
.into_iter()
.map(str::to_owned)
.collect::<Vec<_>>()
);
assert_eq!(rows[0].exit, 0);
assert_eq!(rows[0].origin, crate::opslog::Origin::World);
assert!(rows[0].cwd.ends_with("balls/config.toml"));
}
#[test]
fn an_unlawful_branch_refuses_at_the_write_and_still_logs_the_attempt() {
let dir = tempdir().unwrap();
let world = world_at(&dir);
let ws = dir.path().join("workspaces").join("home");
let state = dir.path().join("ops");
let err = apply(
&read(&world, &ws),
&state,
"T0",
"balls/config",
crate::registry::Client::default(),
)
.unwrap_err();
assert!(err.to_string().contains("landing branch"), "{err}");
assert!(!config_file(&read(&world, &ws).config).exists());
let rows = crate::opslog::tail(&state, 10);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].exit, -3);
}
#[test]
fn lawfulness_is_the_whole_guard_and_it_names_every_way_a_branch_can_fail() {
assert!(lawful("balls/tasks"));
assert!(lawful("balls/agents/corp"));
assert!(!lawful(""));
assert!(!lawful("balls/config"));
assert!(!lawful("two words"));
assert!(!lawful("with\ttab"));
assert!(!lawful("say\"hi\""));
assert!(!lawful("back\\slash"));
}
#[test]
fn a_config_body_yog_did_not_write_degrades_to_the_default_rather_than_guessing() {
assert_eq!(parse_branch(""), None);
assert_eq!(parse_branch("log_level = \"debug\"\n"), None);
assert_eq!(parse_branch("tasks_branch = \"\"\n"), None);
assert_eq!(parse_branch("tasks_branch\n"), None);
assert_eq!(
parse_branch("log_level = \"debug\"\ntasks_branch = \"balls/x\"\n"),
Some("balls/x".to_owned())
);
assert_eq!(
parse_branch("tasks_branch = balls/y\n"),
Some("balls/y".to_owned())
);
}
#[test]
fn an_unwritable_space_refuses_and_says_so_rather_than_reporting_a_branch() {
let dir = tempdir().unwrap();
let world = world_at(&dir);
let ws = dir.path().join("workspaces").join("home");
let state = dir.path().join("ops");
let root = own_root(&world, &ws);
std::fs::create_dir_all(root.parent().unwrap()).unwrap();
std::fs::write(&root, "not a dir").unwrap();
assert!(
apply(
&read(&world, &ws),
&state,
"T0",
"balls/x",
crate::registry::Client::default()
)
.is_err()
);
assert_eq!(crate::opslog::tail(&state, 10)[0].exit, -3);
}