use std::path::{Path, PathBuf};
use super::{LaunchSpec, LaunchSpecError};
fn sample() -> LaunchSpec {
LaunchSpec {
session_id: "11111111-2222-3333-4444-555555555555".to_owned(),
launch_id: "aaaabbbbccccddddeeeeffff00001111".to_owned(),
cwd: PathBuf::from("/workspace/repo"),
program: "/abs/claude".to_owned(),
args: vec!["--resume".to_owned(), "abc".to_owned()],
env_unset: vec!["ANTHROPIC_API_KEY".to_owned(), "GH_TOKEN".to_owned()],
env_set: vec![
("CLAUDE_CONFIG_DIR".to_owned(), "/managed/cfg".to_owned()),
("GH_TOKEN".to_owned(), "gho_secret".to_owned()),
],
}
}
#[cfg(unix)]
fn mode_of(path: &Path) -> Option<u32> {
use std::os::unix::fs::PermissionsExt;
std::fs::metadata(path)
.ok()
.map(|m| m.permissions().mode() & 0o777)
}
#[test]
fn spec_root_nests_under_the_tm_config_home() {
if let Some(root) = LaunchSpec::root() {
assert!(
root.ends_with("launch-specs"),
"spec root must be the launch-specs directory: {}",
root.display()
);
assert!(
root.to_string_lossy().contains("trusty-mpm"),
"spec root must live under the tm config home: {}",
root.display()
);
}
}
#[test]
fn spec_root_at_nests_under_the_named_state_home() {
let root = std::path::Path::new("/tmp/fake-home/.trusty-tools/trusty-mpm");
assert_eq!(LaunchSpec::root_at(root), root.join("launch-specs"));
}
#[test]
fn spec_round_trips_through_the_file() {
let dir = tempfile::tempdir().expect("tempdir");
let spec = sample();
let path = spec.write_in(dir.path()).expect("write");
let read = LaunchSpec::consume(&path).expect("consume");
assert_eq!(read, spec);
}
#[cfg(unix)]
#[test]
fn write_creates_an_owner_only_file_in_an_owner_only_dir() {
let dir = tempfile::tempdir().expect("tempdir");
let root = dir.path().join("launch-specs");
let path = sample().write_in(&root).expect("write");
assert_eq!(mode_of(&path), Some(0o600), "spec file must be owner-only");
assert_eq!(mode_of(&root), Some(0o700), "spec dir must be owner-only");
}
#[test]
fn consume_removes_the_file() {
let dir = tempfile::tempdir().expect("tempdir");
let path = sample().write_in(dir.path()).expect("write");
assert!(path.exists());
let _ = LaunchSpec::consume(&path).expect("consume");
assert!(!path.exists(), "consume must delete the spec it read");
}
#[test]
fn consume_reports_a_missing_spec() {
let dir = tempfile::tempdir().expect("tempdir");
let err = LaunchSpec::consume(&dir.path().join("absent.json"))
.expect_err("a missing spec must error");
assert!(matches!(err, LaunchSpecError::Read { .. }), "got {err:?}");
}
#[test]
fn consume_reports_a_corrupt_spec() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("corrupt.json");
std::fs::write(&path, b"{\"session_id\": ").expect("seed");
let err = LaunchSpec::consume(&path).expect_err("a corrupt spec must error");
assert!(matches!(err, LaunchSpecError::Parse { .. }), "got {err:?}");
}
#[test]
fn verify_accepts_a_spec_just_written() {
let dir = tempfile::tempdir().expect("tempdir");
let path = sample().write_in(dir.path()).expect("write");
assert_eq!(LaunchSpec::verify(&path).expect("verify"), sample());
assert!(path.exists(), "verify must leave the spec in place");
}
#[test]
fn verify_reports_a_corrupt_spec() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("corrupt.json");
std::fs::write(&path, b"not json").expect("seed");
let err = LaunchSpec::verify(&path).expect_err("a corrupt spec must error");
assert!(matches!(err, LaunchSpecError::Parse { .. }), "got {err:?}");
}
#[cfg(unix)]
#[test]
fn write_reports_an_unwritable_directory() {
use std::os::unix::fs::PermissionsExt;
let dir = tempfile::tempdir().expect("tempdir");
let blocked = dir.path().join("blocked");
std::fs::create_dir(&blocked).expect("mkdir");
std::fs::set_permissions(&blocked, std::fs::Permissions::from_mode(0o500)).expect("chmod");
let err = sample()
.write_in(&blocked.join("nested"))
.expect_err("an unwritable root must error");
assert!(
matches!(
err,
LaunchSpecError::Dir { .. } | LaunchSpecError::Write { .. }
),
"got {err:?}"
);
let _ = std::fs::set_permissions(&blocked, std::fs::Permissions::from_mode(0o700));
}
#[test]
fn spec_command_carries_cwd_program_argv_and_env() {
let spec = sample();
let cmd = spec.to_command();
assert_eq!(cmd.get_program(), std::ffi::OsStr::new("/abs/claude"));
let argv: Vec<_> = cmd
.get_args()
.map(|a| a.to_string_lossy().into_owned())
.collect();
assert_eq!(argv, vec!["--resume".to_owned(), "abc".to_owned()]);
assert_eq!(cmd.get_current_dir(), Some(Path::new("/workspace/repo")));
let envs: Vec<(String, Option<String>)> = cmd
.get_envs()
.map(|(k, v)| {
(
k.to_string_lossy().into_owned(),
v.map(|v| v.to_string_lossy().into_owned()),
)
})
.collect();
assert!(
envs.contains(&("ANTHROPIC_API_KEY".to_owned(), None)),
"the API key must be REMOVED, not assigned: {envs:?}"
);
assert!(
envs.contains(&(
"CLAUDE_CONFIG_DIR".to_owned(),
Some("/managed/cfg".to_owned())
)),
"the managed config dir must be assigned: {envs:?}"
);
assert!(
envs.contains(&("GH_TOKEN".to_owned(), Some("gho_secret".to_owned()))),
"a later assignment must win over the earlier unset of the same name: {envs:?}"
);
assert!(
envs.contains(&(
crate::core::harness_root::MANAGED_SESSION_ID_ENV.to_owned(),
Some(spec.session_id.clone())
)),
"#2023 B: the managed session id must reach the child: {envs:?}"
);
}
#[test]
fn spec_command_yields_the_alt_screen_default_to_the_pane() {
let cmd = sample().to_command();
let assigned: Option<String> = cmd
.get_envs()
.find(|(k, _)| k.to_string_lossy() == crate::core::alt_screen::ALT_SCREEN_ENV_VAR)
.and_then(|(_, v)| v.map(|v| v.to_string_lossy().into_owned()));
let inherited = std::env::var_os(crate::core::alt_screen::ALT_SCREEN_ENV_VAR).is_some();
if inherited {
assert!(
assigned.is_none(),
"an operator value in the pane must win, not be overridden: {assigned:?}"
);
} else {
assert_eq!(
assigned.as_deref(),
Some(crate::core::alt_screen::ALT_SCREEN_DEFAULT),
"with nothing inherited the tm default must be provisioned"
);
}
}
fn backdate(path: &Path, age: std::time::Duration) {
let when = std::time::SystemTime::now() - age;
std::fs::OpenOptions::new()
.write(true)
.open(path)
.expect("reopen the orphan")
.set_modified(when)
.expect("backdate the orphan");
}
#[test]
fn write_reaps_an_orphaned_spec() {
let dir = tempfile::tempdir().expect("tempdir");
let orphan = sample().write_in(dir.path()).expect("write the orphan");
backdate(&orphan, std::time::Duration::from_secs(3600));
let fresh = sample().write_in(dir.path()).expect("write a second spec");
assert!(
!orphan.exists(),
"an hour-old spec must be reaped by the next launch: {}",
orphan.display()
);
assert!(
fresh.exists(),
"the launch's own spec must survive its sweep"
);
}
#[test]
fn write_keeps_a_fresh_spec() {
let dir = tempfile::tempdir().expect("tempdir");
let concurrent = sample().write_in(dir.path()).expect("write the sibling");
let mine = sample().write_in(dir.path()).expect("write my spec");
assert!(
concurrent.exists(),
"a seconds-old sibling spec must be left alone: {}",
concurrent.display()
);
assert!(mine.exists());
}
#[test]
fn reap_survives_a_missing_directory() {
let dir = tempfile::tempdir().expect("tempdir");
let absent = dir.path().join("never-created");
super::reap_orphans_in(&absent, std::time::Duration::from_secs(1));
assert!(sample().write_in(&absent).is_ok());
}
#[test]
fn started_marker_is_named_for_the_session() {
let marker = LaunchSpec::started_marker_in(Path::new("/specs"), "sess-1");
assert_eq!(marker, PathBuf::from("/specs/sess-1.started"));
}
#[test]
fn mark_started_writes_the_sentinel_beside_the_spec() {
let dir = tempfile::tempdir().expect("tempdir");
let spec = sample();
let path = spec.write_in(dir.path()).expect("write");
LaunchSpec::mark_started(&path, &spec.session_id);
assert!(
LaunchSpec::started_marker_in(dir.path(), &spec.session_id).exists(),
"the shim must leave a sentinel the daemon can see"
);
}