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)
}
}
struct AlwaysFailingExecutor;
impl CommandExecutor for AlwaysFailingExecutor {
fn execute(&self, _command: &CommandSpec) -> Result<CommandOutput> {
Ok(CommandOutput {
status: 1,
stdout: vec![],
stderr: vec![],
})
}
}
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(),
}
}
#[test]
fn golden_doctor_reports_external_aws_ssh_docker_and_podman_observations() {
let aws = FakeExecutor::new([
Ok(output(
b"aws-cli/2.15.40 Python/3.11.6 Linux/5.10.205 exe/x86_64 prompt/off\n",
)),
Ok(output(
br#"{"UserId":"AIDAEXAMPLE","Account":"123456789012","Arn":"arn:aws:iam::123456789012:user/doctor"}"#,
)),
Ok(output(
br#"{"LaunchTemplates":[{"LaunchTemplateId":"lt-0123456789abcdef0","LaunchTemplateName":"mjolnir","CreateTime":"2026-01-01T00:00:00+00:00","CreatedBy":"arn:aws:iam::123456789012:user/doctor","DefaultVersionNumber":7,"LatestVersionNumber":7}]}"#,
)),
]);
let aws_check = aws_target_check(
"production",
None,
"us-east-2",
"lt-0123456789abcdef0",
&aws,
);
let aws_commands = aws.commands.borrow();
assert_eq!(
aws_commands[1]
.args
.iter()
.map(String::as_str)
.collect::<Vec<_>>(),
[
"--profile",
"default",
"--region",
"us-east-2",
"sts",
"get-caller-identity",
"--output",
"json",
]
);
assert!(
aws_commands[2]
.args
.contains(&"--launch-template-ids".to_owned())
);
drop(aws_commands);
let ssh = RuntimeSshTarget {
destination: "dev@docker.example.test".to_owned(),
ssh_args: Vec::new(),
};
let docker = FakeExecutor::new([
Ok(output(b"")),
Ok(output(b"27.5.1 linux\n")),
Ok(output(
br#"[{"Id":"sha256:0123456789abcdef","RepoTags":["ghcr.io/example/dev:1.2.3"]}]"#,
)),
]);
let docker_check = ssh_docker_check(
"remote-docker",
&ssh,
"ghcr.io/example/dev:1.2.3",
&docker,
false,
);
let docker_commands = docker.commands.borrow();
assert_eq!(docker_commands.len(), 3);
assert!(
docker_commands
.iter()
.all(|command| command.program == "ssh")
);
assert_eq!(
docker_commands[0].args.last().map(String::as_str),
Some("'true'")
);
assert!(
docker_commands[1]
.args
.last()
.is_some_and(|command| command.contains("'docker' 'version' '--format'"))
);
assert!(
docker_commands[2]
.args
.last()
.is_some_and(|command| command.contains("'docker' 'image' 'inspect'"))
);
drop(docker_commands);
let podman = FakeExecutor::new([Ok(output(
b"keys.used=174\nkeys.quota=200\nkeys.max=1000\nmaxstartups=10:30:100\n",
))]);
let podman_check = ssh_podman_limits_check("remote-podman", &ssh, &podman);
let podman_commands = podman.commands.borrow();
assert_eq!(podman_commands[0].purpose, "read ssh-podman host limits");
assert_eq!(podman_commands[0].program, "ssh");
assert!(
podman_commands[0].args.last().is_some_and(
|script| script.contains("/proc/key-users") && script.contains("maxstartups")
)
);
drop(podman_commands);
let missing_ssh = FakeExecutor::new([Err(anyhow!(std::io::Error::new(
std::io::ErrorKind::NotFound,
"ssh executable is unavailable",
)))]);
let missing_ssh_check = ssh_podman_limits_check("missing-ssh", &ssh, &missing_ssh);
let mut config = Config::default();
config.subagents.max_concurrent = 3;
config
.subagents
.eligible_profiles
.insert("disabled-reviewer".to_owned(), true);
config.profiles.insert(
"disabled-reviewer".to_owned(),
HarnessProfile {
enabled: false,
kind: HarnessKind::Codex,
home: PathBuf::from("/home/example/.codex"),
environment: Default::default(),
context_window_bytes: None,
subagents: Default::default(),
guardian_review_model: None,
},
);
let subagent_checks = subagent_eligibility_checks(Ok(&config));
let mut checks = vec![aws_check, docker_check, podman_check, missing_ssh_check];
checks.extend(subagent_checks);
let mut rendered = Vec::new();
render_human(&checks, &mut rendered).expect("render doctor checks");
let rendered = String::from_utf8(rendered).expect("doctor output is UTF-8");
mj_core::golden::assert_golden(
env!("CARGO_MANIFEST_DIR"),
"doctor-external-runtime-checks",
&rendered,
);
}
fn container(image: &str) -> ContainerTemplate {
ContainerTemplate {
build_cache: None,
image: image.to_owned(),
pull_policy: Default::default(),
platform: None,
cpus: None,
memory: None,
environment: Default::default(),
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_eq!(
config.err(),
Some(ConfigGap::NewerVersion(mj_core::config::CONFIG_VERSION + 1))
);
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}");
}
#[test]
fn a_config_without_a_bundle_is_ready_for_sessions() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("config.toml");
let profile = HarnessProfile {
enabled: true,
kind: HarnessKind::Codex,
home: directory.path().join("codex-home"),
environment: Default::default(),
context_window_bytes: None,
subagents: Default::default(),
guardian_review_model: None,
};
Config {
profiles: [("work".to_owned(), profile)].into_iter().collect(),
..Config::default()
}
.save_to(&path)
.unwrap();
let (_, checks) = configuration_checks(&path);
let check = checks
.iter()
.find(|check| check.id == "config.session-prerequisites")
.unwrap();
assert_eq!(check.status, CheckStatus::Ready, "{check:?}");
assert!(all_ready(&checks));
}
#[test]
fn a_config_without_an_enabled_profile_cannot_start_sessions() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("config.toml");
Config::default().save_to(&path).unwrap();
let (_, checks) = configuration_checks(&path);
let check = checks
.iter()
.find(|check| check.id == "config.session-prerequisites")
.unwrap();
assert_eq!(check.status, CheckStatus::Fixable);
assert!(check.detail.contains("profile"), "{}", check.detail);
assert!(!check.detail.contains("bundle"), "{}", check.detail);
}
#[test]
fn checks_waiting_for_a_config_say_how_to_get_one_without_json() {
let directory = tempfile::tempdir().unwrap();
let missing = directory.path().join("config.toml");
let run = |path: &Path| {
run_with_config_path(
path,
&AlwaysFailingExecutor,
ApplePlatform::Linux,
DoctorOptions { smoke: false },
)
};
let find = |checks: &[DoctorCheck], id: &str| {
checks
.iter()
.find(|check| check.id == id)
.unwrap_or_else(|| panic!("{id} is reported"))
.clone()
};
let checks = run(&missing);
for check in &checks {
let text = format!(
"{} {}",
check.detail,
check.remediation.as_deref().unwrap_or_default()
);
assert!(
!text.contains("Fix config.toml")
&& !text.contains("config.toml is valid")
&& !text.contains("--json"),
"{} advises fixing a file that does not exist: {text}",
check.id
);
}
for id in ["harness.profiles", "worker.containers"] {
let check = find(&checks, id);
assert_eq!(check.status, CheckStatus::Fixable, "{id}");
let remediation = check.remediation.unwrap_or_default();
assert!(
remediation.contains("Run `mj`")
&& remediation.contains("`mj setup`")
&& remediation.contains("rerun `mj doctor`"),
"{id}: {remediation}"
);
}
let invalid = directory.path().join("invalid.toml");
std::fs::write(&invalid, "version = \n").unwrap();
let checks = run(&invalid);
for id in ["harness.profiles", "worker.containers"] {
assert_eq!(
find(&checks, id).remediation.as_deref(),
Some("Fix config.toml, then rerun `mj doctor`."),
"{id}"
);
}
}
#[test]
fn a_newer_builds_config_never_asks_the_user_to_fix_config_toml() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("config.toml");
let newer = mj_core::config::CONFIG_VERSION + 1;
std::fs::write(
&path,
format!("version = {newer}\n\n[targets.localhost]\nkind = \"local-bare\"\n"),
)
.unwrap();
let checks = run_with_config_path(
&path,
&AlwaysFailingExecutor,
ApplePlatform::Linux,
DoctorOptions { smoke: false },
);
for check in &checks {
let text = format!(
"{} {}",
check.detail,
check.remediation.as_deref().unwrap_or_default()
);
assert!(
!text.contains("config.toml is valid") && !text.contains("Fix config.toml"),
"{} advises fixing a config that is not broken: {text}",
check.id
);
}
for id in [
"harness.profiles",
"runtime.podman",
"runtime.docker",
"worker.containers",
] {
let check = checks
.iter()
.find(|check| check.id == id)
.unwrap_or_else(|| panic!("{id} is reported"));
assert_eq!(check.status, CheckStatus::Unsupported, "{id}");
assert_eq!(check.remediation, None, "{id}");
assert!(
check
.detail
.contains(&format!("newer Mjolnir (config version {newer}")),
"{id}: {}",
check.detail
);
}
}
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()
}
}
#[test]
fn doctor_warns_once_when_shared_container_host_mbx_is_too_old() {
let config = config_with([
(
"podman",
TargetTemplate::LocalPodman {
container: container("ubuntu:24.04"),
},
),
(
"docker",
TargetTemplate::LocalDocker {
container: container("ubuntu:24.04"),
},
),
]);
let executor = FakeExecutor::new([
Ok(output("Linux x86_64")),
Ok(output("/usr/local/bin/mbx\nmbx 1.15.0")),
]);
let checks = build_cache_checks(Ok(&config), &executor);
assert_eq!(checks.len(), 1);
let check = &checks[0];
assert_eq!(check.id, "build-cache.local");
assert_eq!(check.status, CheckStatus::Warning);
assert!(check.detail.contains("1.15.0"));
assert!(check.detail.contains(crate::controller::MBX_VERSION));
assert!(check.detail.contains("run without the shared build cache"));
assert!(check.detail.contains("docker, podman"));
assert!(
check
.remediation
.as_deref()
.unwrap()
.contains("Upgrade mbx")
);
assert_eq!(executor.commands.borrow().len(), 2);
assert!(
all_ready(&checks),
"an optional cache warning preserves doctor's exit status"
);
let json = serde_json::to_value(check).unwrap();
assert_eq!(json["status"], "warning");
assert!(
json["remediation"]
.as_str()
.unwrap()
.contains(crate::controller::MBX_VERSION)
);
let mut human = Vec::new();
render_human(&checks, &mut human).unwrap();
let human = String::from_utf8(human).unwrap();
assert!(human.contains("warning Build cache on local"));
assert!(human.contains("remediation: Upgrade mbx"));
}
#[test]
fn doctor_distinguishes_compatible_absent_and_uncheckable_host_mbx() {
let config = config_with([(
"podman",
TargetTemplate::LocalPodman {
container: container("ubuntu:24.04"),
},
)]);
for (response, expected_status, expected_text) in [
(
Ok(output(format!(
"/usr/local/bin/mbx\nmbx {}",
crate::controller::MBX_VERSION
))),
CheckStatus::Ready,
"compatible",
),
(
Ok(failed("")),
CheckStatus::Warning,
"run without the shared build cache",
),
(
Err(anyhow!("probe timed out")),
CheckStatus::Warning,
"probe timed out",
),
] {
let executor = FakeExecutor::new([Ok(output("Linux x86_64")), response]);
let checks = build_cache_checks(Ok(&config), &executor);
assert_eq!(checks.len(), 1);
assert_eq!(checks[0].status, expected_status);
assert!(checks[0].detail.contains(expected_text), "{:?}", checks[0]);
}
}
#[test]
fn a_missing_docker_is_reported_as_not_installed() {
let executor = FakeExecutor::new([Err(anyhow::Error::new(std::io::Error::from(
std::io::ErrorKind::NotFound,
))
.context("run docker for check Docker daemon"))]);
let check = local_docker_runtime_check(&executor);
assert_eq!(check.status, CheckStatus::Fixable);
assert_eq!(check.detail, "Docker is not installed on this host.");
assert!(
check
.remediation
.as_deref()
.is_some_and(|remediation| remediation.starts_with("Install Docker")),
"{:?}",
check.remediation
);
}
#[test]
fn docker_checks_cover_the_built_in_docker_target_the_dashboard_lists() {
let executor = FakeExecutor::new([
Ok(output(b"29.0.1 linux\n")),
Ok(output(b"image metadata\n")),
]);
let checks = docker_checks(Ok(&Config::default()), &executor, false);
assert_eq!(
checks
.iter()
.map(|check| (check.id.as_str(), check.status))
.collect::<Vec<_>>(),
vec![
("runtime.docker", CheckStatus::Ready),
("runtime.docker.image.docker", CheckStatus::Ready)
]
);
let missing_image = FakeExecutor::new([Ok(output(b"29.0.1 linux\n")), Ok(failed(b""))]);
let checks = docker_checks(Ok(&Config::default()), &missing_image, false);
assert_eq!(
checks[1].status,
CheckStatus::Warning,
"the dashboard downloads a built-in target's image itself: {}",
checks[1].detail
);
let checks = docker_checks(Ok(&Config::default()), &AlwaysFailingExecutor, false);
assert_eq!(checks.len(), 1);
assert_eq!(checks[0].status, CheckStatus::Unsupported);
assert!(
checks[0].detail.contains("built-in `docker` target"),
"{}",
checks[0].detail
);
assert!(all_ready(&checks));
let configured = config_with([(
"docker",
TargetTemplate::LocalDocker {
container: container("ghcr.io/example/dev:1"),
},
)]);
let checks = docker_checks(Ok(&configured), &AlwaysFailingExecutor, false);
assert_eq!(
checks[0].status,
CheckStatus::Fixable,
"a target the user configured is still a fault to fix"
);
assert!(checks[0].remediation.is_some());
assert!(!all_ready(&checks));
let available = FakeExecutor::new([
Ok(output(b"29.0.1 linux\n")),
Ok(output(b"image metadata\n")),
Ok(output(b"image metadata\n")),
]);
let checks = docker_checks(Ok(&configured), &available, false);
assert!(
checks
.iter()
.all(|check| check.status == CheckStatus::Ready),
"{checks:?}"
);
assert!(all_ready(&checks));
}
#[test]
fn a_target_block_identical_to_a_built_in_is_still_treated_as_built_in() {
let written = config_with([
(
"docker",
TargetTemplate::LocalDocker {
container: container(mj_core::config::DEFAULT_CONTAINER_IMAGE),
},
),
(
"podman",
TargetTemplate::LocalPodman {
container: container(mj_core::config::DEFAULT_CONTAINER_IMAGE),
},
),
]);
let docker = docker_checks(Ok(&written), &AlwaysFailingExecutor, false);
assert_eq!(docker.len(), 1);
assert_eq!(
docker[0].status,
CheckStatus::Unsupported,
"{}",
docker[0].detail
);
assert!(docker[0].detail.contains("built-in `docker` target"));
let podman = podman_checks(
Ok(&written),
&AlwaysFailingExecutor,
false,
&ApplePlatform::Linux,
);
assert_eq!(podman.len(), 1);
assert_eq!(
podman[0].status,
CheckStatus::Unsupported,
"{}",
podman[0].detail
);
let missing_image = FakeExecutor::new([Ok(output(b"29.0.1 linux\n")), Ok(failed(b""))]);
let checks = docker_checks(Ok(&written), &missing_image, false);
assert_eq!(
checks[1].status,
CheckStatus::Warning,
"{}",
checks[1].detail
);
}
#[test]
fn podman_checks_cover_the_built_in_podman_target() {
let checks = podman_checks(
Ok(&Config::default()),
&AlwaysFailingExecutor,
false,
&ApplePlatform::Linux,
);
assert_eq!(checks.len(), 1);
assert_eq!(checks[0].status, CheckStatus::Unsupported);
assert!(
checks[0].detail.contains("built-in `podman` target"),
"{}",
checks[0].detail
);
}
#[cfg(target_os = "linux")]
#[test]
fn docker_desktop_smoke_verifies_the_read_only_attachment() {
let executor = FakeExecutor::new([
Ok(output(b"Docker Desktop 4.40.0 (187762)\n")),
Ok(output(b"created\n")),
Ok(output(b"ok\n")),
Ok(output(b"removed\n")),
Ok(output(b"Docker Desktop 4.40.0 (187762)\n")),
]);
let check = docker_image_check("docker", "ubuntu:24.04", &executor, true);
assert_eq!(check.status, CheckStatus::Ready, "{}", check.detail);
assert!(
check
.detail
.contains("read-only attachment smoke test passed")
);
let commands = executor.commands.borrow();
assert!(commands.iter().all(|command| {
!command
.args
.iter()
.any(|arg| arg.starts_with("type=overlay"))
}));
let probe = commands
.iter()
.find(|command| command.args.first().map(String::as_str) == Some("exec"))
.expect("smoke probe");
assert!(probe.args.last().unwrap().contains("! printf"));
}
#[test]
fn failed_smoke_test_of_a_builtin_target_stays_fixable() {
let failed = DoctorCheck::fixable(
"runtime.docker.image.docker",
"Docker image for target docker",
"Disposable run/exec/remove smoke test failed",
"Fix the configured image or Docker runtime",
);
let config = Config::default().with_local_targets();
let smoke = builtin_image_check(Ok(&config), "docker", failed.clone(), true);
let presence = builtin_image_check(Ok(&config), "docker", failed, false);
assert_eq!(smoke.status, CheckStatus::Fixable);
assert_eq!(presence.status, CheckStatus::Warning);
}
#[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);
assert!(!limits.keyring_is_under_pressure());
let sentence = limits.max_startups_sentence();
assert!(sentence.contains("unreadable drop-in"), "{sentence}");
assert!(!sentence.contains("10:30"), "{sentence}");
let readable_default = parse_host_limits(b"keys.used=10\nkeys.quota=4096\n");
assert!(!readable_default.max_startups_unreadable);
assert_eq!(
readable_default.max_startups_sentence(),
"sshd MaxStartups is not set in sshd_config, so sshd's default applies."
);
let explicit = parse_host_limits(b"maxstartups=10:30:60\n");
assert_eq!(
explicit.max_startups_sentence(),
"sshd MaxStartups is 10:30:60."
);
let empty = parse_host_limits(b"");
assert!(empty.is_empty());
}
#[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 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(Ok(&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);
}
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"),
},
)])
}
fn ssh_podman_probe_executor(linger: (i32, &str, &str)) -> FakeExecutor {
let probes = crate::targets::ssh_podman_probe_fixture(&[
("version", 0, "podman version 5.4.2\n", ""),
(
"uid_map",
0,
" 0 1000 1\n 1 100000 65536\n",
"",
),
("linger", linger.0, linger.1, linger.2),
]);
FakeExecutor::new([Ok(output(b"")), Ok(output(probes))])
}
#[test]
fn ssh_podman_check_explains_when_durability_cannot_be_verified() {
let ssh = RuntimeSshTarget::from(&ssh_connection());
let ready_executor = ssh_podman_probe_executor((0, "yes\n", ""));
let (ready, _) = ssh_podman_check("remote", &ssh, "ubuntu:24.04", &ready_executor, false);
assert_eq!(ready.id, "runtime.ssh-podman.remote");
assert_eq!(ready.title, "Remote Podman for target remote");
assert_eq!(ready.status, CheckStatus::Ready);
assert!(ready.detail.contains("Remote rootless Podman 5.4.2"));
assert!(ready.detail.contains("dev@example.test"));
let commands = ready_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")
);
let disabled_executor = ssh_podman_probe_executor((0, "no\n", ""));
let (disabled, _) = ssh_podman_check("remote", &ssh, "ubuntu:24.04", &disabled_executor, false);
assert_eq!(disabled.status, CheckStatus::Warning);
assert!(all_ready(std::slice::from_ref(&disabled)));
assert!(disabled.detail.contains("Podman 5.4.2 is available"));
assert!(disabled.detail.contains("last SSH connection closes"));
assert!(
disabled
.remediation
.as_deref()
.unwrap()
.contains("sudo loginctl enable-linger")
);
let unknown_executor = ssh_podman_probe_executor((127, "", "sh: loginctl: not found\n"));
let (unknown, _) = ssh_podman_check("remote", &ssh, "ubuntu:24.04", &unknown_executor, false);
assert_eq!(unknown.status, CheckStatus::Warning);
assert!(all_ready(std::slice::from_ref(&unknown)));
assert!(unknown.detail.contains("durability check is unavailable"));
assert!(unknown.detail.contains("may not use systemd"));
assert!(unknown.detail.contains("cannot verify"));
let remediation = unknown.remediation.as_deref().unwrap();
assert!(remediation.contains("service manager"));
assert!(!remediation.contains("sudo loginctl enable-linger"));
}
#[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",
&RuntimeSshTarget::from(&ssh_connection()),
"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 storage_check_reports_a_full_disk_and_its_root_reserve() {
let full = output(
b"home=/home/dev\n\
storage=0\t491134172\t467026656\t/\t.local/share/hel/workers\n\
storage=41943040\t976762584\t900000000\t/home/dev/Projects\t/home/dev/Projects\n\
cpu.percent=3\nmemory.current=1\nmemory.max=2\nlogical.cores=8\n",
);
let roomy = output(b"home=/home/dev\nstorage=41943040\t491134172\t100000000\t/\t/tmp\n");
let executor = FakeExecutor::new([Ok(full)]);
let checks = storage_checks(Ok(&ssh_bare_config()), &executor);
assert_eq!(checks.len(), 1);
assert_eq!(checks[0].id, "storage.ssh:example.test");
assert_eq!(checks[0].status, CheckStatus::Fixable);
assert_eq!(
checks[0].detail,
"Disk full on /: /: 0 B free, 24.69 GB reserved for root (full); \
/home/dev/Projects: 42.95 GB free, 35.66 GB reserved for root. \
Mjolnir refuses writes there, and sessions that write there wait instead of restarting."
);
let probe = executor.commands.borrow()[0].clone();
let remote = probe.args.last().unwrap();
for path in [
".local/share/hel/workers",
".local/share/hel/profiles",
".cache/mjolnir",
".cache/mbx",
"/tmp",
] {
assert!(remote.contains(path), "{path} in {remote}");
}
let executor = FakeExecutor::new([Ok(roomy)]);
let checks = storage_checks(Ok(&ssh_bare_config()), &executor);
assert_eq!(checks[0].status, CheckStatus::Ready, "{}", checks[0].detail);
}
#[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(Ok(&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(Ok(&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_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(Ok(&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_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(Ok(&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(Ok(&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 image_checks_are_skipped_when_the_host_podman_preflight_fails() {
let executor = FakeExecutor::new([Ok(output(b"")), 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(Ok(&config), &executor, false, &ApplePlatform::Linux);
assert_eq!(checks.len(), 1);
assert_eq!(checks[0].id, "runtime.podman");
let mut responses = vec![
Ok(output(b"podman version 5.4.2\n")),
Ok(output(
b" 0 1000 1\n 1 100000 65536\n",
)),
];
responses.extend([Ok(output(b"")), Ok(failed(b"")), Ok(output(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(Ok(&config), &executor, false, &ApplePlatform::Linux);
assert_eq!(
checks
.iter()
.map(|check| check.id.as_str())
.collect::<Vec<_>>(),
vec![
"runtime.podman",
"runtime.podman.image.alpha",
"runtime.podman.image.beta",
"runtime.podman.image.podman"
]
);
assert_eq!(checks[1].status, CheckStatus::Ready);
assert_eq!(checks[2].status, CheckStatus::Fixable);
assert_eq!(checks[3].status, CheckStatus::Ready);
}
#[test]
fn worker_checks_cover_a_built_in_target_whose_engine_is_ready() {
let engines = [
DoctorCheck::ready(
"runtime.podman",
"Rootless Podman",
"Podman 5.7.0 has a valid rootless UID map.",
),
DoctorCheck::unsupported(
"runtime.docker",
"Docker",
"Docker is not available, so the built-in `docker` target is marked unavailable.",
),
];
let offered = offered_targets(&Config::default(), &engines);
let ids = worker_binary_checks(Ok(&offered))
.into_iter()
.map(|check| check.id)
.collect::<Vec<_>>();
assert_eq!(ids, ["worker.podman"]);
assert_eq!(
container_worker_architectures(Ok(&offered)),
[normalized_worker_architecture(std::env::consts::ARCH)]
);
let configured = config_with([(
"pd",
TargetTemplate::LocalPodman {
container: container("example.test/own:latest"),
},
)]);
let engines = [DoctorCheck::unsupported(
"runtime.podman",
"Rootless Podman",
"Podman is not installed.",
)];
let offered = offered_targets(&configured, &engines);
let ids = worker_binary_checks(Ok(&offered))
.into_iter()
.map(|check| check.id)
.collect::<Vec<_>>();
assert_eq!(ids, ["worker.pd"]);
}
#[test]
fn settings_fixes_name_the_bound_settings_key() {
let directory = tempfile::tempdir().unwrap();
let missing = directory.path().join("missing.toml");
let (_, checks) = configuration_checks(&missing);
let empty = Config::default();
let executor = FakeExecutor::new([]);
let checks = checks
.into_iter()
.chain(harness_checks(Ok(&empty), &executor))
.collect::<Vec<_>>();
for check in &checks {
let remediation = check.remediation.as_deref().unwrap_or_default();
assert!(!remediation.contains("F7"), "{remediation}");
}
assert!(
checks
.iter()
.filter_map(|check| check.remediation.as_deref())
.all(|fix| !fix.contains("Settings") || fix.contains("ctrl+b s")),
"{checks:?}"
);
}
#[test]
fn missing_podman_names_its_fix_once_and_links_the_published_guide() {
for response in [
Err(anyhow!("No such file or directory (os error 2)")),
Ok(failed(b"podman: command not found")),
] {
let check =
local_podman_runtime_check(&FakeExecutor::new([response]), &ApplePlatform::Linux);
assert_eq!(check.status, CheckStatus::Fixable);
let remediation = check.remediation.as_deref().unwrap();
let mut human = Vec::new();
render_human(std::slice::from_ref(&check), &mut human).unwrap();
let human = String::from_utf8(human).unwrap();
assert!(!human.contains("docs/PODMAN.md"), "{human}");
assert!(
remediation.contains("https://mjolnir.brokk.ai/podman/"),
"{remediation}"
);
assert_eq!(human.matches("sudo apt install").count(), 1, "{human}");
}
}
#[test]
fn missing_harness_homes_name_every_supported_agent() {
let check = harness_discovery_check_from(&[], false, "ctrl+b s");
assert_eq!(
check.detail,
"No Codex, Claude Code, Kimi Code, Grok Build, Muse Code, or OpenCode home was found in the default or environment-overridden locations."
);
}
#[test]
fn a_worker_rebuilt_after_the_daemon_started_is_reported_as_changed() {
let directory = tempfile::tempdir().unwrap();
let worker = directory.path().join("mj-worker");
std::fs::write(&worker, b"worker").unwrap();
let started_at = "2026-09-17T23:48:36Z";
let started: SystemTime = chrono::DateTime::parse_from_rfc3339(started_at)
.unwrap()
.into();
let file = std::fs::File::options().write(true).open(&worker).unwrap();
file.set_times(std::fs::FileTimes::new().set_modified(started - Duration::from_secs(60)))
.unwrap();
assert!(
!worker_changed_since_daemon_start(&worker, started_at).unwrap(),
"a worker older than the daemon is the one that daemon pinned"
);
file.set_times(std::fs::FileTimes::new().set_modified(started + Duration::from_secs(60)))
.unwrap();
assert!(
worker_changed_since_daemon_start(&worker, started_at).unwrap(),
"a worker rebuilt after the daemon started is not the one it serves"
);
}
#[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.0.0 or newer**"));
assert!(instructions.contains("kind = \"docker\""));
assert!(instructions.contains("--opt type=overlay"));
}
#[test]
fn setup_instructions_name_mjolnir_and_the_local_bare_prerequisites() {
for platform in [InstructionsPlatform::Linux, InstructionsPlatform::Macos] {
let instructions = setup_instructions(platform);
assert!(!instructions.contains("Hel"), "{instructions}");
assert!(instructions.contains("## Local bare runtime"));
assert!(instructions.contains("Node.js 22 or newer and npm"));
assert!(instructions.ends_with('\n'));
assert!(!instructions.contains("](#"), "{instructions}");
assert!(!instructions.contains("keep-id:uid=,"));
assert!(!instructions.contains("Disposable EC2"));
}
let macos = setup_instructions(InstructionsPlatform::Macos);
assert!(!macos.contains("local Podman"), "{macos}");
}
#[test]
fn review_leftovers_are_reported_and_left_alone() {
let repository = tempfile::tempdir().unwrap();
let git_dir = repository.path().join(".git");
std::fs::create_dir_all(git_dir.join("refs/hel")).unwrap();
std::fs::write(git_dir.join("refs/hel/review-capture"), "a".repeat(41)).unwrap();
std::fs::write(
git_dir.join("packed-refs"),
format!("{} refs/hel/review-baseline\n", "b".repeat(40)),
)
.unwrap();
let scratch = git_dir.join("hel-review-index-AbCdEf");
std::fs::write(&scratch, b"scratch").unwrap();
let residue = crate::doctor::review_residue(repository.path());
assert_eq!(
residue.refs,
["refs/hel/review-baseline", "refs/hel/review-capture"]
);
assert_eq!(residue.scratch_indexes, std::slice::from_ref(&scratch));
assert!(
scratch.is_file() && git_dir.join("refs/hel/review-capture").is_file(),
"doctor reports; it never removes anything from a user's repository"
);
assert_eq!(
std::fs::read_to_string(git_dir.join("refs/hel/review-capture")).unwrap(),
"a".repeat(41)
);
assert_eq!(std::fs::read_to_string(&scratch).unwrap(), "scratch");
let clean = tempfile::tempdir().unwrap();
std::fs::create_dir_all(clean.path().join(".git/refs/heads")).unwrap();
let clean_residue = crate::doctor::review_residue(clean.path());
assert!(clean_residue.refs.is_empty());
assert!(clean_residue.scratch_indexes.is_empty());
}
#[test]
fn review_leftovers_skip_managed_checkouts_and_repositories_in_use() {
use crate::controller::test_support::checkpoint_test_session;
use mj_core::state::{
ManagedCheckoutKind, ManagedWorktree, ManagedWorktreeTarget, SessionState,
};
use std::path::PathBuf;
let project = PathBuf::from("/srv/project");
let managed = |id: &str, kind: ManagedCheckoutKind, state: SessionState| {
let directory = match kind {
ManagedCheckoutKind::Clone => "clones",
ManagedCheckoutKind::Worktree => "worktrees",
};
let root = project.join(".mj").join(directory).join(id);
let mut session = checkpoint_test_session(id);
session.state = state;
session.project_directory = Some(root.clone());
session.managed_worktree = Some(ManagedWorktree {
kind,
source_project_directory: project.clone(),
source_repository: project.clone(),
worktree_root: root,
branch: format!("mj/{id}"),
target: ManagedWorktreeTarget::Local,
base_commit: None,
});
session
};
let running_clone = managed("a1", ManagedCheckoutKind::Clone, SessionState::Running);
let stopped_clone = managed("b2", ManagedCheckoutKind::Clone, SessionState::Stopped);
let mut live_bare = checkpoint_test_session("c3");
live_bare.project_directory = Some(PathBuf::from("/home/me/live"));
let mut stopped_bare = checkpoint_test_session("d4");
stopped_bare.state = SessionState::Stopped;
stopped_bare.project_directory = Some(PathBuf::from("/home/me/stopped"));
let sessions = [&running_clone, &stopped_clone, &live_bare, &stopped_bare];
let repositories = crate::doctor::review_residue_repositories(
[project.clone(), PathBuf::from("/srv/other/.mj/clones/e5/")],
&sessions,
);
assert_eq!(
repositories,
[PathBuf::from("/home/me/stopped"), project.clone()],
"only repositories a person works in by hand, and not while a live session uses them"
);
let running_worktree = managed("f6", ManagedCheckoutKind::Worktree, SessionState::Running);
let repositories =
crate::doctor::review_residue_repositories([project.clone()], &[&running_worktree]);
assert!(repositories.is_empty(), "{repositories:?}");
}
#[test]
fn doctor_reports_a_profile_or_target_that_cannot_start_with_the_fix() {
let directory = tempfile::tempdir().unwrap();
let home = directory.path().join("deepseek");
std::fs::create_dir(&home).unwrap();
std::fs::write(
home.join("config.toml"),
"model_provider = \"deepseek\"\n\n[model_providers.deepseek]\n\
base_url = \"https://api.deepseek.com\"\nwire_api = \"responses\"\nenv_key = \"MJ_TEST_UNSET_PROVIDER_KEY\"\n",
)
.unwrap();
let config_path = directory.path().join("config.toml");
std::fs::write(
&config_path,
format!(
"version = {}\n\n[profiles.deepseek]\nkind = \"codex\"\nhome = {:?}\n\n\
[targets.boxed]\nkind = \"podman\"\nimage = \"example\"\n\n\
[targets.boxed.environment]\nTOKEN = {{ from_secret = \"TOKEN\" }}\n",
mj_core::config::CONFIG_VERSION,
home.to_string_lossy()
),
)
.unwrap();
let config = Config::load_from(&config_path).unwrap();
let profile = harness_checks(Ok(&config), &AlwaysFailingExecutor)
.into_iter()
.find(|check| check.id == "harness.deepseek")
.expect("the profile is reported");
assert_eq!(profile.status, CheckStatus::Fixable);
assert!(
profile
.detail
.contains("export MJ_TEST_UNSET_PROVIDER_KEY and run `mj daemon restart`"),
"{}",
profile.detail
);
let target = secret_checks(Ok(&config), &config_path)
.into_iter()
.find(|check| check.id == "targets.boxed.environment")
.expect("the target is reported");
assert_eq!(target.status, CheckStatus::Fixable);
assert!(
target.detail.contains("TOKEN = { from_secret"),
"{}",
target.detail
);
}
#[test]
fn doctor_points_plain_text_credentials_at_the_secrets_file_and_checks_its_mode() {
use mj_core::config::{Environment, EnvironmentValue, SecretResolver, with_secret_resolver};
let directory = tempfile::tempdir().unwrap();
let config_path = directory.path().join("config.toml");
let secrets = directory.path().join(mj_core::config::SECRETS_FILE);
let mut config = Config::default();
let mut literal = Environment::new();
literal.insert("ZAI_API_KEY".into(), "plain".into());
literal.insert("PATH".into(), "/usr/bin".into());
config.profiles.insert(
"plain".into(),
HarnessProfile {
enabled: true,
kind: HarnessKind::Codex,
home: "/profiles/plain".into(),
environment: literal,
context_window_bytes: None,
subagents: Default::default(),
guardian_review_model: None,
},
);
let referenced = with_secret_resolver(
SecretResolver::fixed(
Default::default(),
[("ZAI_API_KEY".to_owned(), "stored".to_owned())].into(),
),
|| {
Environment::from_sources(
[(
"ZAI_API_KEY".to_owned(),
EnvironmentValue::FromSecret("ZAI_API_KEY".into()),
)]
.into(),
)
},
);
config.profiles.insert(
"referenced".into(),
HarnessProfile {
enabled: true,
kind: HarnessKind::Codex,
home: "/profiles/referenced".into(),
environment: referenced,
context_window_bytes: None,
subagents: Default::default(),
guardian_review_model: None,
},
);
let mut target = container("ghcr.io/example/agent:latest");
target.environment.insert("GH_TOKEN".into(), "plain".into());
config.targets.insert(
"podman".into(),
TargetTemplate::LocalPodman { container: target },
);
let checks = secret_checks(Ok(&config), &config_path);
let ids: Vec<&str> = checks.iter().map(|check| check.id.as_str()).collect();
assert_eq!(
ids,
["profiles.plain.secrets", "targets.podman.secrets"],
"{checks:#?}"
);
assert!(
checks
.iter()
.all(|check| check.status == CheckStatus::Warning)
);
let remediation = checks[0].remediation.as_deref().unwrap();
assert!(
remediation.contains("from_secret") && remediation.contains(&secrets.display().to_string()),
"{remediation}"
);
assert!(checks[0].detail.contains("ZAI_API_KEY") && !checks[0].detail.contains("PATH"));
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::write(&secrets, "ZAI_API_KEY = \"stored\"\n").unwrap();
std::fs::set_permissions(&secrets, std::fs::Permissions::from_mode(0o644)).unwrap();
let file_check = secret_checks(Ok(&config), &config_path).remove(0);
assert_eq!(file_check.id, "secrets.file");
assert_eq!(file_check.status, CheckStatus::Warning);
assert!(
file_check
.remediation
.as_deref()
.unwrap()
.contains("chmod 600")
);
std::fs::set_permissions(&secrets, std::fs::Permissions::from_mode(0o600)).unwrap();
let file_check = secret_checks(Ok(&config), &config_path).remove(0);
assert_eq!(file_check.status, CheckStatus::Ready, "{file_check:?}");
}
}