use std::cell::RefCell;
use std::path::PathBuf;
use anyhow::anyhow;
use super::*;
use crate::targets::CommandOutput;
struct FakeExecutor {
commands: RefCell<Vec<CommandSpec>>,
responses: RefCell<Vec<Result<CommandOutput>>>,
}
impl FakeExecutor {
fn new(responses: impl IntoIterator<Item = Result<CommandOutput>>) -> Self {
Self {
commands: RefCell::new(vec![]),
responses: RefCell::new(responses.into_iter().collect()),
}
}
}
impl CommandExecutor for FakeExecutor {
fn execute(&self, command: &CommandSpec) -> Result<CommandOutput> {
self.commands.borrow_mut().push(command.clone());
self.responses.borrow_mut().remove(0)
}
}
fn output(stdout: impl AsRef<[u8]>) -> CommandOutput {
CommandOutput {
status: 0,
stdout: stdout.as_ref().to_vec(),
stderr: vec![],
}
}
fn failed(stderr: impl AsRef<[u8]>) -> CommandOutput {
CommandOutput {
status: 1,
stdout: vec![],
stderr: stderr.as_ref().to_vec(),
}
}
fn reachable_then(
responses: impl IntoIterator<Item = Result<CommandOutput>>,
) -> Vec<Result<CommandOutput>> {
let mut all = vec![Ok(output(b""))];
all.extend(responses);
all
}
fn ssh_podman_probes(linger: (i32, &str, &str)) -> Vec<Result<CommandOutput>> {
vec![Ok(output(crate::targets::ssh_podman_probe_fixture(&[
("version", 0, "podman version 5.4.2\n", ""),
("rootless", 0, "true\n", ""),
(
"uid_map",
0,
" 0 1000 1\n 1 100000 65536\n",
"",
),
("linger", linger.0, linger.1, linger.2),
])))]
}
fn passing_ssh_podman_probes() -> Vec<Result<CommandOutput>> {
ssh_podman_probes((0, "yes\n", ""))
}
fn passing_podman_probes() -> Vec<Result<CommandOutput>> {
vec![
Ok(output(b"podman version 5.4.2\n")),
Ok(output(b"true\n")),
Ok(output(
b" 0 1000 1\n 1 100000 65536\n",
)),
]
}
fn container(image: &str) -> ContainerTemplate {
ContainerTemplate {
build_cache: None,
image: image.to_owned(),
pull_policy: Default::default(),
platform: None,
cpus: None,
memory: None,
environment: std::collections::BTreeMap::new(),
workspace_storage: Default::default(),
}
}
fn ssh_connection() -> mj_core::config::SshConnection {
mj_core::config::SshConnection {
host: "example.test".into(),
user: Some("dev".into()),
identity_file: None,
extra_args: vec![],
}
}
#[test]
fn doctor_tells_the_user_to_update_rather_than_replace_a_newer_builds_config() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("config.toml");
std::fs::write(
&path,
format!(
"version = {}\n\n[targets.localhost]\nkind = \"local-bare\"\n",
mj_core::config::CONFIG_VERSION + 1
),
)
.unwrap();
let (config, checks) = configuration_checks(&path);
assert!(config.is_none());
let check = checks.iter().find(|check| check.id == "config").unwrap();
assert_eq!(check.status, CheckStatus::Fixable);
assert!(check.detail.contains("newer Mjolnir"), "{}", check.detail);
let remediation = check.remediation.as_deref().unwrap_or_default();
assert!(remediation.contains("Update Mjolnir"), "{remediation}");
assert!(!remediation.contains("mj setup"), "{remediation}");
}
fn config_with(targets: impl IntoIterator<Item = (&'static str, TargetTemplate)>) -> Config {
Config {
targets: targets
.into_iter()
.map(|(id, target)| (id.to_owned(), target))
.collect(),
..Config::default()
}
}
fn runtime_ssh() -> RuntimeSshTarget {
RuntimeSshTarget::from(&ssh_connection())
}
#[test]
fn podman_check_is_unsupported_without_a_valid_config() {
let executor = FakeExecutor::new([]);
let check = podman_check(None, &executor);
assert_eq!(check.status, CheckStatus::Unsupported);
assert_eq!(
check.detail,
"Podman prerequisites cannot be evaluated until config.toml is valid."
);
assert!(executor.commands.borrow().is_empty());
}
#[test]
fn podman_check_is_unsupported_without_a_local_podman_target() {
let executor = FakeExecutor::new([]);
let config = config_with([(
"apple",
TargetTemplate::AppleContainer {
container: container("ubuntu:24.04"),
},
)]);
let check = podman_check(Some(&config), &executor);
assert_eq!(check.status, CheckStatus::Unsupported);
assert_eq!(check.detail, "No local-podman target is configured.");
assert!(executor.commands.borrow().is_empty());
}
#[test]
fn podman_check_probes_the_host_when_a_local_podman_target_exists() {
let executor = FakeExecutor::new(passing_podman_probes());
let config = config_with([(
"podman",
TargetTemplate::LocalPodman {
container: container("ubuntu:24.04"),
},
)]);
let check = podman_check(Some(&config), &executor);
assert_eq!(check.status, CheckStatus::Ready);
assert!(check.detail.contains("Podman 5.4.2"));
assert_eq!(executor.commands.borrow().len(), 3);
}
#[test]
fn podman_check_is_fixable_with_an_upgrade_remediation_for_an_old_runtime() {
let executor = FakeExecutor::new(reachable_then([Ok(output(b"podman version 3.4.7\n"))]));
let config = config_with([(
"podman",
TargetTemplate::LocalPodman {
container: container("ubuntu:24.04"),
},
)]);
let check = podman_check(Some(&config), &executor);
assert_eq!(check.status, CheckStatus::Fixable);
assert!(
check
.remediation
.as_deref()
.unwrap()
.contains("Install or upgrade Podman")
);
}
#[test]
fn podman_image_check_is_ready_when_the_image_is_present() {
let executor = FakeExecutor::new([Ok(output(b""))]);
let check = podman_image_check("podman", "localhost/hel/agent-dev:latest", &executor, false);
assert_eq!(check.id, "runtime.podman.image.podman");
assert_eq!(check.title, "Podman image for target podman");
assert_eq!(check.status, CheckStatus::Ready);
assert_eq!(
executor.commands.borrow()[0].args,
vec!["image", "exists", "localhost/hel/agent-dev:latest"]
);
}
#[test]
fn podman_image_check_is_fixable_with_a_pull_remediation_when_the_image_is_missing() {
let executor = FakeExecutor::new([Ok(failed(b""))]);
let check = podman_image_check("podman", "ghcr.io/example/dev:1", &executor, false);
assert_eq!(check.status, CheckStatus::Fixable);
assert!(
check
.detail
.contains("is not present in local Podman storage")
);
assert_eq!(
check.remediation.as_deref(),
Some(
"Pull it with `podman pull ghcr.io/example/dev:1`, build it from containers/Containerfile.agent-dev, or run `mj doctor --json --smoke` to verify the full pull-and-run path."
)
);
}
#[test]
fn podman_image_check_smoke_runs_a_disposable_container() {
let executor = FakeExecutor::new([
Ok(output(b"created\n")),
Ok(output(b"ok\n")),
Ok(output(b"removed\n")),
]);
let check = podman_image_check("podman", "ubuntu:24.04", &executor, true);
assert_eq!(check.status, CheckStatus::Ready);
let commands = executor.commands.borrow();
assert_eq!(commands.len(), 3);
assert!(commands.iter().all(|command| command.program == "podman"));
assert_eq!(commands[0].args[0], "run");
assert_eq!(commands[1].args[0], "exec");
assert_eq!(commands[2].args[0], "rm");
}
#[test]
fn image_checks_are_skipped_when_the_host_podman_preflight_fails() {
let executor = FakeExecutor::new(reachable_then([Ok(output(b"podman version 3.4.7\n"))]));
let config = config_with([(
"podman",
TargetTemplate::LocalPodman {
container: container("ubuntu:24.04"),
},
)]);
let checks = podman_checks(Some(&config), &executor, false);
assert_eq!(checks.len(), 1);
assert_eq!(checks[0].id, "runtime.podman");
}
#[test]
fn image_checks_follow_a_passing_preflight_for_each_local_podman_target() {
let mut responses = passing_podman_probes();
responses.push(Ok(output(b"")));
responses.push(Ok(failed(b"")));
let executor = FakeExecutor::new(responses);
let config = config_with([
(
"alpha",
TargetTemplate::LocalPodman {
container: container("ubuntu:24.04"),
},
),
(
"beta",
TargetTemplate::LocalPodman {
container: container("ghcr.io/example/dev:1"),
},
),
]);
let checks = podman_checks(Some(&config), &executor, false);
assert_eq!(
checks
.iter()
.map(|check| check.id.as_str())
.collect::<Vec<_>>(),
vec![
"runtime.podman",
"runtime.podman.image.alpha",
"runtime.podman.image.beta"
]
);
assert_eq!(checks[1].status, CheckStatus::Ready);
assert_eq!(checks[2].status, CheckStatus::Fixable);
}
#[test]
fn docker_checks_probe_the_daemon_then_the_configured_image() {
let executor = FakeExecutor::new([
Ok(output(b"29.0.1 linux\n")),
Ok(output(b"image metadata\n")),
]);
let config = config_with([(
"docker",
TargetTemplate::LocalDocker {
container: container("ghcr.io/example/dev:1"),
},
)]);
let checks = docker_checks(Some(&config), &executor, false);
assert_eq!(
checks
.iter()
.map(|check| check.id.as_str())
.collect::<Vec<_>>(),
vec!["runtime.docker", "runtime.docker.image.docker"]
);
assert!(
checks
.iter()
.all(|check| check.status == CheckStatus::Ready)
);
let commands = executor.commands.borrow();
assert_eq!(commands[0].program, "docker");
assert_eq!(
commands[0].args,
["version", "--format", "{{.Server.Version}} {{.Server.Os}}"]
);
assert_eq!(
commands[1].args,
["image", "inspect", "ghcr.io/example/dev:1"]
);
}
#[test]
fn docker_image_smoke_uses_managed_overlay_run_exec_and_cleanup() {
let executor = FakeExecutor::new([
Ok(output(b"created\n")),
Ok(output(b"ok\n")),
Ok(output(b"removed\n")),
]);
let check = docker_image_check("docker", "ubuntu:24.04", &executor, true);
assert_eq!(check.status, CheckStatus::Ready);
let commands = executor.commands.borrow();
assert_eq!(commands.len(), 3);
assert_eq!(commands[0].program, "sh");
assert!(commands[0].args[1].contains("docker volume create"));
assert!(commands[0].args.contains(&"--pull=missing".to_owned()));
assert_eq!(commands[1].program, "docker");
assert_eq!(commands[1].args[0], "exec");
assert_eq!(commands[2].program, "sh");
assert!(commands[2].args[1].contains("docker rm --force"));
assert!(commands[2].args[1].contains("docker volume rm --force"));
}
#[test]
fn ssh_podman_check_is_ready_after_ssh_wrapped_probes_without_smoke() {
let executor = FakeExecutor::new(reachable_then(passing_ssh_podman_probes()));
let (check, _) = ssh_podman_check("remote", &runtime_ssh(), "ubuntu:24.04", &executor, false);
assert_eq!(check.id, "runtime.ssh-podman.remote");
assert_eq!(check.title, "Remote Podman for target remote");
assert_eq!(check.status, CheckStatus::Ready);
assert!(check.detail.contains("Remote rootless Podman 5.4.2"));
assert!(check.detail.contains("dev@example.test"));
let commands = executor.commands.borrow();
assert_eq!(commands.len(), 2);
assert_eq!(commands[0].args.last().unwrap(), "'true'");
for command in commands.iter().skip(1) {
assert_eq!(command.program, "ssh");
assert!(command.args.contains(&"dev@example.test".to_owned()));
}
assert!(
commands[1]
.args
.last()
.unwrap()
.contains("loginctl show-user")
);
}
#[test]
fn ssh_podman_check_warns_when_remote_user_lingering_is_disabled() {
let executor = FakeExecutor::new(reachable_then(ssh_podman_probes((0, "no\n", ""))));
let (check, _) = ssh_podman_check("remote", &runtime_ssh(), "ubuntu:24.04", &executor, false);
assert_eq!(check.status, CheckStatus::Warning);
assert!(all_ready(std::slice::from_ref(&check)));
assert!(check.detail.contains("Podman 5.4.2 is available"));
assert!(check.detail.contains("last SSH connection closes"));
assert!(
check
.remediation
.as_deref()
.unwrap()
.contains("sudo loginctl enable-linger")
);
}
#[test]
fn ssh_podman_check_explains_when_durability_cannot_be_verified() {
let executor = FakeExecutor::new(reachable_then(ssh_podman_probes((
127,
"",
"sh: loginctl: not found\n",
))));
let (check, _) = ssh_podman_check("remote", &runtime_ssh(), "ubuntu:24.04", &executor, false);
assert_eq!(check.status, CheckStatus::Warning);
assert!(all_ready(std::slice::from_ref(&check)));
assert!(check.detail.contains("durability check is unavailable"));
assert!(check.detail.contains("may not use systemd"));
assert!(check.detail.contains("cannot verify"));
let remediation = check.remediation.as_deref().unwrap();
assert!(remediation.contains("service manager"));
assert!(!remediation.contains("sudo loginctl enable-linger"));
}
#[test]
fn ssh_podman_check_failure_scopes_the_remediation_to_the_remote_host() {
let executor = FakeExecutor::new(reachable_then([Ok(output(
crate::targets::ssh_podman_probe_fixture(&[("version", 0, "podman version 3.4.7\n", "")]),
))]));
let (check, _) = ssh_podman_check("remote", &runtime_ssh(), "ubuntu:24.04", &executor, false);
assert_eq!(check.status, CheckStatus::Fixable);
assert!(check.detail.contains("dev@example.test"));
assert!(
check
.remediation
.as_deref()
.unwrap()
.starts_with("On dev@example.test: Install or upgrade Podman")
);
}
#[test]
fn ssh_podman_check_reports_the_shared_ssh_remediation_before_probing_podman() {
let executor = FakeExecutor::new([Ok(failed(
b"dev@example.test: Permission denied (publickey).",
))]);
let (check, _) = ssh_podman_check("remote", &runtime_ssh(), "ubuntu:24.04", &executor, false);
assert_eq!(check.status, CheckStatus::Fixable);
assert_eq!(
check.remediation.as_deref(),
Some("Install your public key on the host with `ssh-copy-id dev@example.test`.")
);
assert_eq!(executor.commands.borrow().len(), 1);
}
#[test]
fn ssh_podman_check_smoke_runs_an_ssh_wrapped_disposable_container() {
let mut responses = passing_ssh_podman_probes();
responses.extend([
Ok(output(b"created\n")),
Ok(output(b"ok\n")),
Ok(output(b"removed\n")),
]);
let executor = FakeExecutor::new(reachable_then(responses));
let (check, _) = ssh_podman_check("remote", &runtime_ssh(), "ubuntu:24.04", &executor, true);
assert_eq!(check.status, CheckStatus::Ready);
let commands = executor.commands.borrow();
assert_eq!(commands.len(), 5);
for command in commands.iter().skip(2) {
assert_eq!(command.program, "ssh");
assert!(command.args.contains(&"dev@example.test".to_owned()));
}
assert!(commands[2].args.last().unwrap().contains("'run' '--init'"));
assert!(commands[3].args.last().unwrap().ends_with("'true'"));
assert!(commands[4].args.last().unwrap().contains("'rm' '--force'"));
}
fn host_limits_output() -> Vec<u8> {
b"keys.max=200\nkeys.used=101\nkeys.quota=4096\nmaxstartups=100:30:200\n".to_vec()
}
#[test]
fn host_limits_parse_reports_every_field_the_script_printed() {
let limits = parse_host_limits(&host_limits_output());
assert_eq!(limits.keys_max, Some(200));
assert_eq!(limits.keys_used, Some(101));
assert_eq!(limits.keys_quota, Some(4096));
assert_eq!(limits.max_startups.as_deref(), Some("100:30:200"));
assert!(!limits.max_startups_unreadable);
assert!(!limits.is_empty());
assert!(!limits.keyring_is_under_pressure());
}
#[test]
fn host_limits_report_pressure_when_keys_reach_the_quota() {
let limits = parse_host_limits(b"keys.used=3300\nkeys.quota=4096\n");
assert!(limits.keyring_is_under_pressure());
assert!(
limits
.keyring_sentence("dev@example.test")
.contains("3300 of its 4096")
);
}
#[test]
fn host_limits_report_an_explicit_max_startups_directive() {
let limits = parse_host_limits(b"maxstartups=10:30:60\n");
assert_eq!(
limits.max_startups_sentence(),
"sshd MaxStartups is 10:30:60."
);
}
#[test]
fn host_limits_say_a_drop_in_may_override_an_unread_max_startups() {
let limits = parse_host_limits(b"keys.used=10\nkeys.quota=4096\nmaxstartups.unreadable=1\n");
assert!(limits.max_startups_unreadable);
let sentence = limits.max_startups_sentence();
assert!(sentence.contains("unreadable drop-in"), "{sentence}");
assert!(!sentence.contains("10:30"), "{sentence}");
}
#[test]
fn host_limits_say_the_sshd_default_applies_when_every_file_was_readable() {
let limits = parse_host_limits(b"keys.used=10\nkeys.quota=4096\n");
assert_eq!(
limits.max_startups_sentence(),
"sshd MaxStartups is not set in sshd_config, so sshd's default applies."
);
}
#[test]
fn host_limits_are_empty_when_the_script_printed_nothing() {
assert!(parse_host_limits(b"").is_empty());
assert!(parse_host_limits(b"unrelated line\n").is_empty());
}
#[test]
fn ssh_podman_checks_report_host_limits_after_the_podman_check() {
let mut responses = passing_ssh_podman_probes();
responses.push(Ok(output(host_limits_output())));
let executor = FakeExecutor::new(reachable_then(responses));
let config = config_with([(
"remote",
TargetTemplate::SshPodman {
ssh: ssh_connection(),
container: container("ubuntu:24.04"),
},
)]);
let checks = ssh_podman_checks(Some(&config), &executor, false);
assert_eq!(checks.len(), 2);
assert_eq!(checks[0].id, "runtime.ssh-podman.remote");
assert_eq!(checks[1].id, "runtime.ssh-podman.remote.limits");
assert_eq!(checks[1].title, "Host limits for target remote");
assert_eq!(checks[1].status, CheckStatus::Ready);
assert!(
checks[1].detail.contains("101 of its 4096"),
"{}",
checks[1].detail
);
assert!(
checks[1].detail.contains("sshd MaxStartups is 100:30:200"),
"{}",
checks[1].detail
);
let commands = executor.commands.borrow();
assert_eq!(commands.len(), 3);
assert!(commands[2].args.last().unwrap().contains("key-users"));
}
#[test]
fn ssh_podman_checks_skip_host_limits_when_the_host_is_unreachable() {
let executor = FakeExecutor::new([Ok(failed(
b"dev@example.test: Permission denied (publickey).",
))]);
let config = config_with([(
"remote",
TargetTemplate::SshPodman {
ssh: ssh_connection(),
container: container("ubuntu:24.04"),
},
)]);
let checks = ssh_podman_checks(Some(&config), &executor, false);
assert_eq!(checks.len(), 1);
assert_eq!(checks[0].id, "runtime.ssh-podman.remote");
assert_eq!(checks[0].status, CheckStatus::Fixable);
assert_eq!(executor.commands.borrow().len(), 1);
}
#[test]
fn ssh_docker_check_probes_connectivity_daemon_and_remote_image() {
let executor = FakeExecutor::new([
Ok(output(b"")),
Ok(output(b"29.0.1 linux\n")),
Ok(output(b"image metadata\n")),
]);
let check = ssh_docker_check(
"remote",
&runtime_ssh(),
"ghcr.io/example/dev:1",
&executor,
false,
);
assert_eq!(check.id, "runtime.ssh-docker.remote");
assert_eq!(check.title, "Remote Docker for target remote");
assert_eq!(check.status, CheckStatus::Ready);
assert!(check.detail.contains("Remote Docker 29.0.1"));
assert!(
check
.detail
.contains("image ghcr.io/example/dev:1 is present")
);
let commands = executor.commands.borrow();
assert_eq!(commands.len(), 3);
assert!(commands.iter().all(|command| command.program == "ssh"));
assert!(commands[0].args.last().unwrap().contains("'true'"));
assert!(
commands[1]
.args
.last()
.unwrap()
.contains("'docker' 'version'")
);
assert!(
commands[2]
.args
.last()
.unwrap()
.contains("'docker' 'image' 'inspect'")
);
}
#[test]
fn ssh_docker_check_smoke_runs_overlay_on_the_remote_host() {
let executor = FakeExecutor::new([
Ok(output(b"")),
Ok(output(b"29.0.1 linux\n")),
Ok(output(b"/tmp/mj-docker-overlay-smoke.fixture\n")),
Ok(output(b"created\n")),
Ok(output(b"ok\n")),
Ok(output(b"verified\n")),
Ok(output(b"removed\n")),
Ok(output(b"removed\n")),
]);
let check = ssh_docker_check("remote", &runtime_ssh(), "ubuntu:24.04", &executor, true);
assert_eq!(check.status, CheckStatus::Ready);
assert!(check.detail.contains("remote OverlayFS attachment"));
let commands = executor.commands.borrow();
assert_eq!(commands.len(), 8);
assert!(commands.iter().all(|command| command.program == "ssh"));
assert!(commands[2].args.last().unwrap().contains("mktemp"));
assert!(commands[3].args.last().unwrap().contains("'docker' 'run'"));
assert!(commands[4].args.last().unwrap().contains("'docker' 'exec'"));
assert!(commands[5].args.last().unwrap().contains("original.txt"));
assert!(commands[6].args.last().unwrap().contains("docker rm"));
assert!(commands[7].args.last().unwrap().contains("'rm' '-rf'"));
}
#[test]
fn ssh_podman_checks_are_skipped_without_a_valid_config() {
let executor = FakeExecutor::new([]);
assert!(ssh_podman_checks(None, &executor, false).is_empty());
assert!(executor.commands.borrow().is_empty());
}
fn ssh_bare_config() -> Config {
config_with([(
"builder",
TargetTemplate::SshBare {
ssh: mj_core::config::SshConnection {
host: "example.test".into(),
user: Some("dev".into()),
identity_file: Some(PathBuf::from("/home/dev/.ssh/id_ed25519")),
extra_args: vec![],
},
permissions: mj_core::config::PermissionMode::Yolo,
workspace_prefix: PathBuf::from(".local/share/hel/workspaces"),
},
)])
}
#[test]
fn ssh_bare_check_is_ready_when_the_batch_mode_probe_succeeds() {
let executor = FakeExecutor::new([Ok(output(b""))]);
let checks = ssh_bare_checks(Some(&ssh_bare_config()), &executor);
assert_eq!(checks.len(), 1);
assert_eq!(checks[0].id, "runtime.ssh-bare.builder");
assert_eq!(checks[0].status, CheckStatus::Ready);
let command = &executor.commands.borrow()[0];
assert_eq!(command.program, "ssh");
assert_eq!(
command.args[..4],
["-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=yes"]
);
assert!(command.args.contains(&"dev@example.test".to_owned()));
assert_eq!(command.args.last().unwrap(), "'true'");
}
#[test]
fn ssh_bare_check_permission_denied_recommends_ssh_copy_id_with_the_identity() {
let executor = FakeExecutor::new([Ok(failed(
b"dev@example.test: Permission denied (publickey).",
))]);
let checks = ssh_bare_checks(Some(&ssh_bare_config()), &executor);
assert_eq!(checks[0].status, CheckStatus::Fixable);
assert_eq!(
checks[0].remediation.as_deref(),
Some(
"Install your public key on the host with `ssh-copy-id -i /home/dev/.ssh/id_ed25519.pub dev@example.test`."
)
);
}
#[test]
fn ssh_bare_check_host_key_failure_recommends_keyscan_with_a_fingerprint_caution() {
let executor = FakeExecutor::new([Ok(failed(
b"Host key verification failed.\nNo ECDSA host key is known for example.test",
))]);
let checks = ssh_bare_checks(Some(&ssh_bare_config()), &executor);
assert_eq!(checks[0].status, CheckStatus::Fixable);
let remediation = checks[0].remediation.as_deref().unwrap();
assert!(
remediation.contains("ssh-keyscan -H example.test >> ~/.ssh/known_hosts"),
"{remediation}"
);
assert!(
remediation.contains("Verify the fingerprint"),
"{remediation}"
);
}
#[test]
fn ssh_bare_check_without_an_ssh_client_recommends_installing_openssh() {
let executor = FakeExecutor::new([Err(anyhow::Error::new(std::io::Error::from(
std::io::ErrorKind::NotFound,
))
.context("run ssh for verify SSH connectivity"))]);
let checks = ssh_bare_checks(Some(&ssh_bare_config()), &executor);
assert_eq!(checks[0].status, CheckStatus::Fixable);
assert_eq!(
checks[0].remediation.as_deref(),
Some(SSH_MISSING_REMEDIATION)
);
}
#[test]
fn ssh_bare_check_probe_timeout_recommends_checking_the_host_is_reachable() {
let executor = FakeExecutor::new([Err(anyhow::Error::new(CommandTimedOut {
program: "ssh".into(),
purpose: "verify SSH connectivity".into(),
timeout: std::time::Duration::from_secs(15),
}))]);
let checks = ssh_bare_checks(Some(&ssh_bare_config()), &executor);
assert_eq!(checks[0].status, CheckStatus::Fixable);
let remediation = checks[0].remediation.as_deref().unwrap();
assert!(
remediation.contains("example.test is up and reachable"),
"{remediation}"
);
assert!(!remediation.contains("openssh-client"), "{remediation}");
assert!(
checks[0]
.detail
.contains("did not answer within 15 seconds"),
"{}",
checks[0].detail
);
}
#[test]
fn ssh_bare_check_connect_timeout_recommends_checking_the_host_is_reachable() {
let executor = FakeExecutor::new([Ok(failed(
b"ssh: connect to host example.test port 22: Connection timed out",
))]);
let checks = ssh_bare_checks(Some(&ssh_bare_config()), &executor);
let remediation = checks[0].remediation.as_deref().unwrap();
assert!(
remediation.contains("example.test is up and reachable"),
"{remediation}"
);
}
#[test]
fn ssh_bare_check_falls_back_to_quoting_an_unrecognized_ssh_failure() {
let executor = FakeExecutor::new([Ok(failed(
b"kex_exchange_identification: read: Connection reset by peer",
))]);
let checks = ssh_bare_checks(Some(&ssh_bare_config()), &executor);
let remediation = checks[0].remediation.as_deref().unwrap();
assert!(
remediation.contains("Connection reset by peer"),
"{remediation}"
);
assert!(
remediation.contains("Run `ssh dev@example.test true` by hand"),
"{remediation}"
);
}
#[test]
fn ssh_bare_check_other_launch_failure_falls_back_to_running_ssh_by_hand() {
let executor = FakeExecutor::new([Err(anyhow!(
"operation cancelled while verify SSH connectivity"
))]);
let checks = ssh_bare_checks(Some(&ssh_bare_config()), &executor);
assert_eq!(checks[0].status, CheckStatus::Fixable);
let remediation = checks[0].remediation.as_deref().unwrap();
assert!(
remediation.contains("Run `ssh dev@example.test true` by hand"),
"{remediation}"
);
assert!(!remediation.contains("openssh-client"), "{remediation}");
}
#[test]
fn ssh_bare_checks_are_skipped_without_a_valid_config() {
let executor = FakeExecutor::new([]);
assert!(ssh_bare_checks(None, &executor).is_empty());
assert!(executor.commands.borrow().is_empty());
}
#[test]
fn worker_check_for_an_ssh_podman_target_without_platform_is_unsupported() {
let config = config_with([(
"remote",
TargetTemplate::SshPodman {
ssh: ssh_connection(),
container: container("ubuntu:24.04"),
},
)]);
let checks = worker_binary_checks(Some(&config));
assert_eq!(checks.len(), 1);
assert_eq!(checks[0].id, "worker.remote");
assert_eq!(checks[0].status, CheckStatus::Unsupported);
assert_eq!(
checks[0].detail,
"Set `platform` on this ssh-podman target to check its worker binary; the remote architecture is unknown until provisioning."
);
}
#[test]
fn worker_check_for_an_ssh_podman_target_with_platform_uses_the_normal_check() {
let mut remote = container("ubuntu:24.04");
remote.platform = Some("linux/amd64".into());
let config = config_with([(
"remote",
TargetTemplate::SshPodman {
ssh: ssh_connection(),
container: remote,
},
)]);
let checks = worker_binary_checks(Some(&config));
assert_eq!(checks.len(), 1);
assert_eq!(checks[0].id, "worker.remote");
assert_ne!(checks[0].status, CheckStatus::Unsupported);
assert!(checks[0].detail.contains("x86_64-unknown-linux-musl"));
}
#[test]
fn worker_check_for_an_ssh_docker_target_hints_at_remote_architecture() {
let config = config_with([(
"remote",
TargetTemplate::SshDocker {
ssh: ssh_connection(),
container: container("ubuntu:24.04"),
},
)]);
let checks = worker_binary_checks(Some(&config));
assert_eq!(checks.len(), 1);
assert_eq!(checks[0].status, CheckStatus::Unsupported);
assert_eq!(
checks[0].detail,
"Set `platform` on this ssh-docker target to check its worker binary; the remote architecture is unknown until provisioning."
);
}
#[test]
fn an_unauthenticated_profile_is_fixed_by_hel_login_for_that_profile() {
let directory = tempfile::tempdir().unwrap();
let home = directory.path().join("claude-home");
std::fs::create_dir_all(&home).unwrap();
let profile = HarnessProfile {
enabled: true,
kind: HarnessKind::Claude,
home,
environment: std::collections::BTreeMap::new(),
context_window_bytes: None,
guardian_review_model: None,
};
let config = Config {
profiles: [("work".to_owned(), profile.clone())].into_iter().collect(),
..Config::default()
};
let executor = FakeExecutor::new([Ok(output(br#"{"loggedIn":false}"#))]);
let checks = harness_checks(Some(&config), &executor);
assert_eq!(checks.len(), 1);
assert_eq!(checks[0].status, CheckStatus::Fixable);
let remediation = checks[0].remediation.as_deref().unwrap();
assert!(
remediation.contains("mj login --profile work"),
"{remediation}"
);
let (program, arguments) = login_command(&profile).expect("OAuth profile has a login command");
assert!(
remediation.contains(&format!("`{program} {}`", arguments.join(" "))),
"{remediation}"
);
}
#[test]
fn doctor_reports_disabled_profiles_without_probing_them() {
let profile = HarnessProfile {
enabled: false,
kind: HarnessKind::Claude,
home: PathBuf::from("/missing/disabled-profile"),
environment: std::collections::BTreeMap::new(),
context_window_bytes: None,
guardian_review_model: None,
};
let config = Config {
profiles: [("retired".to_owned(), profile)].into_iter().collect(),
..Config::default()
};
let executor = FakeExecutor::new([]);
let checks = harness_checks(Some(&config), &executor);
assert_eq!(checks.len(), 1);
assert_eq!(checks[0].status, CheckStatus::Ready);
assert!(checks[0].detail.contains("disabled"));
assert!(executor.commands.borrow().is_empty());
}
#[test]
fn harness_discovery_reports_each_authentication_state() {
let check = harness_discovery_check_from(
&[
DiscoveredHome {
kind: HarnessKind::Codex,
path: "/agents/codex".into(),
authenticated: true,
},
DiscoveredHome {
kind: HarnessKind::Kimi,
path: "/agents/kimi".into(),
authenticated: false,
},
],
true,
);
assert_eq!(check.status, CheckStatus::Ready);
assert!(
check
.detail
.contains("Codex at /agents/codex (authenticated)")
);
assert!(
check
.detail
.contains("Kimi Code at /agents/kimi (not authenticated)")
);
}
#[test]
fn missing_harness_homes_are_fixable_without_a_configured_profile() {
let check = harness_discovery_check_from(&[], false);
assert_eq!(check.status, CheckStatus::Fixable);
assert_eq!(
check.remediation.as_deref(),
Some("Install and sign in to a supported harness, then open F7 Settings → Agent Profiles.")
);
}
#[test]
fn apple_container_is_unsupported_on_intel_macs() {
let executor = FakeExecutor::new([]);
let check = apple_container_check(
&ApplePlatform::Macos {
architecture: "x86_64".into(),
major_version: 26,
},
&executor,
false,
DEFAULT_CONTAINER_IMAGE.into(),
);
assert_eq!(check.status, CheckStatus::Unsupported);
assert!(check.detail.contains("Intel Macs"));
assert!(executor.commands.borrow().is_empty());
}
#[test]
fn apple_container_is_unsupported_before_macos_26() {
let executor = FakeExecutor::new([]);
let check = apple_container_check(
&ApplePlatform::Macos {
architecture: "aarch64".into(),
major_version: 25,
},
&executor,
false,
DEFAULT_CONTAINER_IMAGE.into(),
);
assert_eq!(check.status, CheckStatus::Unsupported);
assert!(check.detail.contains("macOS 26"));
}
#[test]
fn apple_container_not_installed_has_official_package_remediation() {
let executor = FakeExecutor::new([Err(anyhow!("No such file or directory"))]);
let check = apple_container_check(
&ApplePlatform::Macos {
architecture: "aarch64".into(),
major_version: 26,
},
&executor,
false,
DEFAULT_CONTAINER_IMAGE.into(),
);
assert_eq!(check.status, CheckStatus::Fixable);
assert_eq!(
check.remediation.as_deref(),
Some(
format!("Install the official signed package: {APPLE_CONTAINER_INSTALL_URL}").as_str()
)
);
}
#[test]
fn apple_container_stopped_daemon_has_start_remediation() {
let executor = FakeExecutor::new([
Ok(output(b"container version 1\n")),
Ok(CommandOutput {
status: 1,
stdout: vec![],
stderr: b"daemon is not running".to_vec(),
}),
]);
let check = apple_container_check(
&ApplePlatform::Macos {
architecture: "aarch64".into(),
major_version: 26,
},
&executor,
false,
DEFAULT_CONTAINER_IMAGE.into(),
);
assert_eq!(check.status, CheckStatus::Fixable);
assert_eq!(
check.remediation.as_deref(),
Some("Run `container system start`.")
);
}
#[test]
fn apple_container_is_ready_only_after_the_opt_in_smoke_test() {
let executor = FakeExecutor::new([
Ok(output(b"container version 1\n")),
Ok(output(b"running\n")),
Ok(output(b"created\n")),
Ok(output(b"ok\n")),
Ok(output(b"removed\n")),
]);
let check = apple_container_check(
&ApplePlatform::Macos {
architecture: "aarch64".into(),
major_version: 26,
},
&executor,
true,
DEFAULT_CONTAINER_IMAGE.into(),
);
assert_eq!(check.status, CheckStatus::Ready);
assert_eq!(executor.commands.borrow().len(), 5);
assert_eq!(executor.commands.borrow()[2].args[0], "run");
assert_eq!(executor.commands.borrow()[3].args[0], "exec");
assert_eq!(executor.commands.borrow()[4].args[0], "rm");
}
#[test]
fn linux_reports_apple_container_as_macos_only() {
let executor = FakeExecutor::new([]);
let check = apple_container_check(
&ApplePlatform::Linux,
&executor,
false,
DEFAULT_CONTAINER_IMAGE.into(),
);
assert_eq!(check.status, CheckStatus::Unsupported);
assert_eq!(check.detail, "macOS only");
}
fn aws_target(launch_template: &str) -> TargetTemplate {
TargetTemplate::AwsEc2 {
aws_profile: Some("hel".into()),
region: "us-east-1".into(),
launch_template: launch_template.to_owned(),
launch_template_version: None,
ssh_user: "ubuntu".into(),
address_source: mj_core::config::AwsAddressSource::default(),
identity_file: None,
ssh_args: vec![],
}
}
#[test]
fn aws_check_is_ready_after_the_cli_credential_and_launch_template_probes() {
let executor = FakeExecutor::new([
Ok(output(b"aws-cli/2.17.0\n")),
Ok(output(b"{\"Account\":\"123456789012\"}\n")),
Ok(output(b"{\"LaunchTemplates\":[{}]}\n")),
]);
let config = config_with([("aws", aws_target("hel-runson"))]);
let checks = aws_checks(Some(&config), &executor);
assert_eq!(checks.len(), 1);
assert_eq!(checks[0].id, "runtime.aws-ec2.aws");
assert_eq!(checks[0].status, CheckStatus::Ready);
let commands = executor.commands.borrow();
assert!(commands.iter().all(|command| command.program == "aws"));
assert_eq!(
commands[2].args,
vec![
"--profile",
"hel",
"--region",
"us-east-1",
"ec2",
"describe-launch-templates",
"--launch-template-names",
"hel-runson",
"--output",
"json"
]
);
}
#[test]
fn aws_check_is_fixable_with_an_install_remediation_without_the_cli() {
let executor = FakeExecutor::new([Err(anyhow!("No such file or directory"))]);
let check = aws_target_check("aws", None, "us-east-1", "hel-runson", &executor);
assert_eq!(check.status, CheckStatus::Fixable);
assert!(
check
.remediation
.as_deref()
.unwrap()
.contains(AWS_CLI_INSTALL_URL)
);
assert_eq!(executor.commands.borrow().len(), 1);
}
#[test]
fn aws_check_is_fixable_with_a_sign_in_remediation_for_expired_credentials() {
let executor = FakeExecutor::new([
Ok(output(b"aws-cli/2.17.0\n")),
Ok(failed(b"ExpiredToken: the security token has expired")),
]);
let check = aws_target_check("aws", Some("hel"), "us-east-1", "hel-runson", &executor);
assert_eq!(check.status, CheckStatus::Fixable);
assert!(check.detail.contains("ExpiredToken"));
assert_eq!(
check.remediation.as_deref(),
Some(
"Configure credentials with `aws configure --profile hel`, or sign in with `aws sso login --profile hel`."
)
);
}
#[test]
fn aws_check_is_fixable_when_the_launch_template_is_missing() {
let executor = FakeExecutor::new([
Ok(output(b"aws-cli/2.17.0\n")),
Ok(output(b"{\"Account\":\"123456789012\"}\n")),
Ok(failed(b"InvalidLaunchTemplateName.NotFoundException")),
]);
let check = aws_target_check("aws", Some("hel"), "us-east-1", "lt-0123456789", &executor);
assert_eq!(check.status, CheckStatus::Fixable);
assert!(check.detail.contains("was not found in us-east-1"));
assert_eq!(
executor.commands.borrow()[2].args[6],
"--launch-template-ids"
);
}
#[test]
fn aws_checks_are_skipped_for_configs_without_an_aws_target() {
let executor = FakeExecutor::new([]);
let config = config_with([(
"podman",
TargetTemplate::LocalPodman {
container: container("ubuntu:24.04"),
},
)]);
assert!(aws_checks(Some(&config), &executor).is_empty());
assert!(aws_checks(None, &executor).is_empty());
assert!(executor.commands.borrow().is_empty());
}
#[test]
fn apple_container_daemon_check_is_ready_once_the_daemon_answers() {
let executor = FakeExecutor::new([
Ok(output(b"container version 1\n")),
Ok(output(b"running\n")),
]);
let check = apple_container_daemon_check(&executor);
assert_eq!(check.status, CheckStatus::Ready);
assert_eq!(executor.commands.borrow().len(), 2);
}
#[test]
fn linux_instructions_embed_podman_postconditions_and_doctor_loop() {
let instructions = setup_instructions(InstructionsPlatform::Linux);
assert!(instructions.contains("mj doctor --json"));
assert!(instructions.contains("mj doctor --json --smoke"));
assert!(instructions.contains("podman unshare cat /proc/self/uid_map"));
assert!(instructions.contains("Podman **4.3.0 or newer**"));
assert!(instructions.contains("kind = \"local-docker\""));
assert!(instructions.contains("--opt type=overlay"));
}