use std::path::PathBuf;
use std::time::Duration;
use super::support::make_ctx;
use crate::commands::{status, up};
use crate::fs::Fs;
use crate::packs::orchestration::ExecutionContext;
use crate::paths::Pather;
use crate::shell::activation::{self, INIT_GEN_ENV};
use crate::shell::{ProbePolicy, ShellEnv};
use crate::testing::TempEnvironment;
fn env_with_shell_pack() -> TempEnvironment {
TempEnvironment::builder()
.pack("vim")
.file("aliases.sh", "alias vi=vim")
.done()
.build()
}
fn spawn_marker(env: &TempEnvironment) -> PathBuf {
env.home.join("probe-ran.marker")
}
fn fake_shell(env: &TempEnvironment, rc_behaviour: &str) -> PathBuf {
let path = env.home.join("fake-bin/zsh");
env.fs.mkdir_all(path.parent().unwrap()).unwrap();
let script = format!(
"#!/bin/sh\n: > {marker}\n{rc_behaviour}\neval \"$2\"\n",
marker = spawn_marker(env).display(),
);
env.fs
.write_file_with_mode(&path, script.as_bytes(), 0o755)
.unwrap();
path
}
fn probing_ctx(env: &TempEnvironment, rc_behaviour: &str) -> ExecutionContext {
let shell = fake_shell(env, rc_behaviour);
let mut ctx = make_ctx(env);
ctx.shell_probe = ProbePolicy::Gated {
timeout: Duration::from_secs(10),
};
ctx.shell_env = ShellEnv {
shell: Some(shell.display().to_string()),
zdotdir: None,
};
ctx
}
fn sources_dodot(env: &TempEnvironment) -> String {
format!(". \"{}\"", env.paths.init_script_path().display())
}
fn simulate_activation(env: &TempEnvironment, generation: u64) {
env.fs.mkdir_all(&env.paths.probes_hookup_dir()).unwrap();
env.fs
.write_file(
&env.paths.hookup_heartbeat_path(),
generation.to_string().as_bytes(),
)
.unwrap();
}
#[test]
fn a_healthy_machine_never_spawns_a_probe() {
let env = env_with_shell_pack();
up::up(None, &make_ctx(&env)).unwrap();
let generation =
activation::read_script_generation(env.fs.as_ref(), env.paths.as_ref()).unwrap();
simulate_activation(&env, generation);
let ctx = probing_ctx(&env, &sources_dodot(&env));
let result = up::up(None, &ctx).unwrap();
assert!(
!env.fs.exists(&spawn_marker(&env)),
"a fresh heartbeat is proof; spawning a shell anyway is the cost we promised not to pay"
);
assert_eq!(result.shell_hookup, None);
}
#[test]
fn a_live_shell_is_proof_enough_on_its_own() {
let env = env_with_shell_pack();
up::up(None, &make_ctx(&env)).unwrap();
let generation =
activation::read_script_generation(env.fs.as_ref(), env.paths.as_ref()).unwrap();
let mut ctx = probing_ctx(&env, &sources_dodot(&env));
ctx.env_init_gen = Some(generation);
up::up(None, &ctx).unwrap();
assert!(
!env.fs.exists(&spawn_marker(&env)),
"the calling shell carries a current stamp — nothing left to measure"
);
}
#[test]
fn status_never_spawns_a_shell_whatever_the_policy_says() {
let env = env_with_shell_pack();
up::up(None, &make_ctx(&env)).unwrap();
let ctx = probing_ctx(&env, &sources_dodot(&env));
let result = status::status(None, &ctx).unwrap();
assert!(!env.fs.exists(&spawn_marker(&env)));
assert_eq!(
result.shell_hookup.map(|n| n.state),
Some("never-activated".into()),
"status still reports, from evidence alone"
);
}
#[test]
fn a_dry_run_measures_nothing() {
let env = env_with_shell_pack();
let mut ctx = probing_ctx(&env, &sources_dodot(&env));
ctx.dry_run = true;
up::up(None, &ctx).unwrap();
assert!(
!env.fs.exists(&spawn_marker(&env)),
"a dry run wrote no script; measuring would report on a world it did not create"
);
}
#[test]
fn a_first_up_ends_on_a_measured_verdict_when_the_shell_activates() {
let env = env_with_shell_pack();
let ctx = probing_ctx(&env, &sources_dodot(&env));
let result = up::up(None, &ctx).unwrap();
assert!(
env.fs.exists(&spawn_marker(&env)),
"the probe must have run"
);
let notice = result
.shell_hookup
.expect("a measured verdict is the point of the first up");
assert_eq!(notice.state, "healthy");
assert_eq!(notice.severity, "ok");
assert!(
notice.message.contains("verified"),
"measured, not inferred: {}",
notice.message
);
}
#[test]
fn a_first_up_whose_shell_does_not_activate_names_the_missing_hook() {
let env = env_with_shell_pack();
let ctx = probing_ctx(&env, "# no dodot hook here");
let result = up::up(None, &ctx).unwrap();
let notice = result.shell_hookup.expect("a broken hookup is news");
assert_eq!(notice.state, "verified-broken");
assert_eq!(notice.severity, "error");
let hint = notice.hint.unwrap();
assert!(
hint.contains("dodot install --write"),
"the fix must be named: {hint}"
);
}
#[test]
fn a_measured_broken_hookup_beats_the_stale_shell_guess() {
let env = env_with_shell_pack();
up::up(None, &make_ctx(&env)).unwrap();
let generation =
activation::read_script_generation(env.fs.as_ref(), env.paths.as_ref()).unwrap();
simulate_activation(&env, generation - 1);
let ctx = probing_ctx(&env, "# the hook line was deleted");
let result = up::up(None, &ctx).unwrap();
let notice = result.shell_hookup.unwrap();
assert_eq!(notice.state, "verified-broken");
assert!(
!notice.hint.unwrap().contains("Open a new shell"),
"no new shell fixes a deleted hook line"
);
}
#[test]
fn an_unverifiable_probe_degrades_instead_of_wedging_up() {
let env = env_with_shell_pack();
let mut ctx = probing_ctx(&env, &sources_dodot(&env));
ctx.shell_env = ShellEnv {
shell: Some(env.home.join("not-a-real-shell").display().to_string()),
zdotdir: None,
};
let result = up::up(None, &ctx).expect("a failed probe must never wedge `up`");
let notice = result
.shell_hookup
.expect("evidence still has something to say");
assert_eq!(
notice.state, "never-activated",
"degrade to the evidence answer"
);
let hint = notice.hint.unwrap();
assert!(
hint.contains("could not verify") && hint.contains("not measured activation"),
"the degradation must be labeled, not passed off as a measurement: {hint}"
);
}
#[test]
fn the_probed_shell_does_not_inherit_this_process_stamp() {
let env = env_with_shell_pack();
let _guard = crate::testing::EnvVarGuard::set(INIT_GEN_ENV, "999999");
let ctx = probing_ctx(&env, "# sources nothing");
let result = up::up(None, &ctx).unwrap();
assert_eq!(
result.shell_hookup.map(|n| n.state),
Some("verified-broken".into())
);
}