use clap::Parser;
use crate::cli::{
AgentAction, CatalogAction, Cli, Command, DEFAULT_URL, DoctorFlags, McpCmd, McpTransportArg,
MemoryAction, MetaAction, ProjectAction, SessionAction, ShellArg, SlackCmd, TelegramCmd,
};
use crate::commands::misc::{CATALOG_STALE_MSG, CATALOG_UNKNOWN_MSG, NON_GIT_FALLBACK_HINT};
use crate::formatters::banner::{
fallback_session_name, normalize_workdir, print_launch_banner,
print_launch_banner_reconnecting, terminal_width,
};
use crate::formatters::session::{delegation_roster_line, deploy_summary_line, short_id};
#[test]
fn session_start_roster_line_is_plain_when_the_roster_is_whole() {
assert_eq!(
delegation_roster_line(38, &[]),
"Instructions: 38 agents in delegation authority"
);
}
#[test]
fn session_start_roster_line_declares_an_incomplete_roster() {
let line = delegation_roster_line(38, &[std::path::PathBuf::from("/p/.claude/agents/qa.md")]);
assert!(
line.contains("ROSTER INCOMPLETE"),
"the line must declare itself incomplete: {line}"
);
assert!(
line.contains("at least 38"),
"the count must not read as a total: {line}"
);
assert!(
line.contains("qa.md"),
"the lost path must be named: {line}"
);
}
#[test]
fn deploy_summary_line_formats_counts() {
assert_eq!(
deploy_summary_line("Skills", 3, 1, 2),
"Skills: 3 deployed, 1 skipped, 2 unchanged"
);
assert_eq!(
deploy_summary_line("Agents", 0, 0, 0),
"Agents: 0 deployed, 0 skipped, 0 unchanged"
);
}
#[test]
fn short_id_reads_the_daemon_string_wire_form() {
let value = serde_json::json!("0f1e2d3c-4b5a-4697-8a8b-9c0d1e2f3a4b");
assert_eq!(
short_id(&value),
"0f1e2d3c",
"the bare-string wire form must render its uuid prefix, never the placeholder"
);
}
#[test]
fn short_id_extracts_uuid_prefix() {
let value = serde_json::json!({"0": "abcd1234-5678-90ab-cdef-1234567890ab"});
assert_eq!(short_id(&value), "abcd1234");
}
#[test]
fn short_id_truncates_to_eight_chars() {
let value = serde_json::json!({"0": "0123456789abcdef-rest-ignored"});
assert_eq!(short_id(&value).chars().count(), 8);
}
#[test]
fn short_id_falls_back_when_field_missing() {
assert_eq!(short_id(&serde_json::json!({})), "--------");
assert_eq!(short_id(&serde_json::json!(null)), "--------");
assert_eq!(short_id(&serde_json::json!(42)), "--------");
}
#[test]
fn short_id_falls_back_when_value_not_str() {
assert_eq!(short_id(&serde_json::json!({"0": 42})), "--------");
}
#[test]
fn cli_parses_status() {
let cli = Cli::try_parse_from(["trusty-mpm", "status"]).unwrap();
assert!(matches!(cli.command.unwrap(), Command::Status));
}
#[test]
fn cli_parses_internal_spawn_disclaimed() {
let cli =
Cli::try_parse_from(["trusty-mpm", "internal-spawn-disclaimed", "claude", "-p"]).unwrap();
let Some(Command::InternalSpawnDisclaimed { launch_spec, argv }) = cli.command else {
panic!("expected internal-spawn-disclaimed");
};
assert_eq!(argv, ["claude", "-p"]);
assert!(launch_spec.is_none(), "the argv form carries no spec");
}
#[test]
fn cli_parses_internal_spawn_disclaimed_launch_spec() {
let cli = Cli::try_parse_from([
"trusty-mpm",
"internal-spawn-disclaimed",
"--launch-spec",
"/specs/abc.json",
])
.unwrap();
let Some(Command::InternalSpawnDisclaimed { launch_spec, argv }) = cli.command else {
panic!("expected internal-spawn-disclaimed");
};
assert_eq!(
launch_spec.as_deref(),
Some(std::path::Path::new("/specs/abc.json"))
);
assert!(
argv.is_empty(),
"the spec form takes no trailing argv: {argv:?}"
);
}
#[test]
fn cli_parses_mcp_add() {
let cli =
Cli::try_parse_from(["trusty-mpm", "mcp", "add", "echo", "--", "echo", "hi"]).unwrap();
let Some(Command::Mcp {
cmd:
McpCmd::Add {
name,
transport,
env,
header,
command_and_args,
root,
project: _,
share_with_projects: _,
},
}) = cli.command
else {
panic!("expected mcp add");
};
assert_eq!(name, "echo");
assert_eq!(transport, McpTransportArg::Stdio);
assert!(env.is_empty() && header.is_empty() && root.is_none());
assert_eq!(command_and_args, vec!["echo", "hi"]);
}
#[test]
fn cli_parses_mcp_add_env_and_root() {
let cli = Cli::try_parse_from([
"trusty-mpm",
"mcp",
"add",
"srv",
"-e",
"A=1",
"-e",
"B=2",
"--root",
"/x",
"--",
"npx",
"pkg",
])
.unwrap();
let Some(Command::Mcp {
cmd:
McpCmd::Add {
env,
root,
command_and_args,
..
},
}) = cli.command
else {
panic!("expected mcp add");
};
assert_eq!(env, vec!["A=1", "B=2"]);
assert_eq!(root.as_deref(), Some("/x"));
assert_eq!(command_and_args, vec!["npx", "pkg"]);
}
#[test]
fn cli_parses_mcp_add_http() {
let cli = Cli::try_parse_from([
"trusty-mpm",
"mcp",
"add",
"sentry",
"-t",
"http",
"-H",
"Authorization: Bearer t",
"https://x/mcp",
])
.unwrap();
let Some(Command::Mcp {
cmd:
McpCmd::Add {
transport,
header,
command_and_args,
..
},
}) = cli.command
else {
panic!("expected mcp add http");
};
assert_eq!(transport, McpTransportArg::Http);
assert_eq!(header, vec!["Authorization: Bearer t"]);
assert_eq!(command_and_args, vec!["https://x/mcp"]);
}
#[test]
fn cli_parses_mcp_share() {
let cli = Cli::try_parse_from(["trusty-mpm", "mcp", "share", "slack-mcp"]).unwrap();
let Some(Command::Mcp {
cmd: McpCmd::Share { name, .. },
}) = cli.command
else {
panic!("expected mcp share");
};
assert_eq!(name, "slack-mcp");
let add = Cli::try_parse_from(["trusty-mpm", "mcp", "add", "x", "--", "echo", "hi"]).unwrap();
let Some(Command::Mcp {
cmd: McpCmd::Add {
share_with_projects,
..
},
}) = add.command
else {
panic!("expected mcp add");
};
assert!(
!share_with_projects,
"registering a server must not share it by default"
);
}
#[test]
fn cli_parses_mcp_unshare() {
let cli = Cli::try_parse_from(["trusty-mpm", "mcp", "unshare", "slack-mcp"]).unwrap();
let Some(Command::Mcp {
cmd: McpCmd::Unshare { name, .. },
}) = cli.command
else {
panic!("expected mcp unshare");
};
assert_eq!(name, "slack-mcp");
let add = Cli::try_parse_from([
"trusty-mpm",
"mcp",
"add",
"--share-with-projects",
"x",
"--",
"echo",
"hi",
])
.unwrap();
let Some(Command::Mcp {
cmd: McpCmd::Add {
share_with_projects,
..
},
}) = add.command
else {
panic!("expected mcp add");
};
assert!(share_with_projects);
}
#[test]
fn cli_parses_mcp_remove_list_get() {
let rm = Cli::try_parse_from(["trusty-mpm", "mcp", "remove", "echo"]).unwrap();
assert!(matches!(
rm.command,
Some(Command::Mcp {
cmd: McpCmd::Remove { .. }
})
));
let ls = Cli::try_parse_from(["trusty-mpm", "mcp", "list", "--json"]).unwrap();
let Some(Command::Mcp {
cmd: McpCmd::List { json, .. },
}) = ls.command
else {
panic!("expected mcp list");
};
assert!(json);
let get = Cli::try_parse_from(["trusty-mpm", "mcp", "get", "echo"]).unwrap();
assert!(matches!(
get.command,
Some(Command::Mcp {
cmd: McpCmd::Get { .. }
})
));
}
#[test]
fn cli_parses_mcp_test() {
let bare = Cli::try_parse_from(["trusty-mpm", "mcp", "test"]).unwrap();
let Some(Command::Mcp {
cmd: McpCmd::Test { name, json, root },
}) = bare.command
else {
panic!("expected mcp test");
};
assert!(name.is_none() && !json && root.is_none());
let named = Cli::try_parse_from([
"trusty-mpm",
"mcp",
"test",
"trusty-memory",
"--json",
"--root",
"/x",
])
.unwrap();
let Some(Command::Mcp {
cmd: McpCmd::Test { name, json, root },
}) = named.command
else {
panic!("expected mcp test named");
};
assert_eq!(name.as_deref(), Some("trusty-memory"));
assert!(json);
assert_eq!(root.as_deref(), Some("/x"));
}
#[test]
fn cli_parses_start() {
let cli = Cli::try_parse_from(["trusty-mpm", "start"]).unwrap();
assert!(matches!(cli.command.unwrap(), Command::Start));
}
#[test]
fn cli_parses_serve() {
let cli = Cli::try_parse_from(["trusty-mpm", "serve"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Serve { stdio: false }
));
}
#[test]
fn cli_parses_serve_stdio() {
let cli = Cli::try_parse_from(["trusty-mpm", "serve", "--stdio"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Serve { stdio: true }
));
}
#[test]
fn cli_parses_stop() {
let cli = Cli::try_parse_from(["trusty-mpm", "stop"]).unwrap();
assert!(matches!(cli.command.unwrap(), Command::Stop));
}
#[test]
fn cli_parses_doctor() {
let cli = Cli::try_parse_from(["trusty-mpm", "doctor"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Doctor {
flags: DoctorFlags {
prune_stale_skills: false,
fix_skills: false,
fix_agents: false,
fix_launchd_secrets: false,
fix: false,
yes: false,
include_frozen: false,
quarantine_mcp: None,
dir: None,
network: false,
}
}
));
}
#[test]
fn cli_parses_doctor_network() {
let cli = Cli::try_parse_from(["trusty-mpm", "doctor", "--network"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Doctor {
flags: DoctorFlags { network: true, .. }
}
));
let bare = Cli::try_parse_from(["trusty-mpm", "doctor"]).unwrap();
assert!(matches!(
bare.command.unwrap(),
Command::Doctor {
flags: DoctorFlags { network: false, .. }
}
));
}
#[test]
fn cli_parses_doctor_dir_and_refuses_it_beside_a_write_flag() {
let cli = Cli::try_parse_from(["trusty-mpm", "doctor", "--dir", "/tmp/x"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Doctor { flags: DoctorFlags { dir: Some(d), .. } } if d == std::path::Path::new("/tmp/x")
));
assert!(Cli::try_parse_from(["trusty-mpm", "doctor", "--dir", "/tmp/x", "--fix"]).is_err());
}
#[test]
fn cli_parses_doctor_prune_stale_skills() {
let cli = Cli::try_parse_from(["trusty-mpm", "doctor", "--prune-stale-skills"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Doctor {
flags: DoctorFlags {
prune_stale_skills: true,
fix_skills: false,
fix_agents: false,
fix_launchd_secrets: false,
fix: false,
yes: false,
include_frozen: false,
quarantine_mcp: None,
dir: None,
network: false,
}
}
));
}
#[test]
fn cli_parses_doctor_fix_skills() {
let cli = Cli::try_parse_from(["trusty-mpm", "doctor", "--fix-skills"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Doctor {
flags: DoctorFlags {
prune_stale_skills: false,
fix_skills: true,
fix_agents: false,
fix_launchd_secrets: false,
fix: false,
yes: false,
include_frozen: false,
quarantine_mcp: None,
dir: None,
network: false,
}
}
));
let cli =
Cli::try_parse_from(["trusty-mpm", "doctor", "--fix-skills", "--include-frozen"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Doctor {
flags: DoctorFlags {
fix_skills: true,
include_frozen: true,
..
}
}
));
assert!(
Cli::try_parse_from(["trusty-mpm", "doctor", "--include-frozen"]).is_err(),
"--include-frozen without --fix-skills must be rejected"
);
let cli = Cli::try_parse_from(["trusty-mpm", "doctor", "--fix-skills", "--yes"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Doctor {
flags: DoctorFlags {
fix_skills: true,
yes: true,
..
}
}
));
assert!(
Cli::try_parse_from(["trusty-mpm", "doctor", "--yes"]).is_err(),
"--yes without a write action must still be rejected"
);
}
#[test]
fn cli_parses_doctor_fix_agents() {
let cli = Cli::try_parse_from(["trusty-mpm", "doctor", "--fix-agents"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Doctor {
flags: DoctorFlags {
fix_agents: true,
fix_skills: false,
fix: false,
yes: false,
..
}
}
));
let cli = Cli::try_parse_from(["trusty-mpm", "doctor", "--fix-agents", "--yes"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Doctor {
flags: DoctorFlags {
fix_agents: true,
yes: true,
..
}
}
));
assert!(
Cli::try_parse_from(["trusty-mpm", "doctor", "--fix-agents", "--include-frozen"]).is_err(),
"--include-frozen requires a skill repair, not --fix-agents"
);
}
#[test]
fn cli_parses_doctor_fix() {
let cli = Cli::try_parse_from(["trusty-mpm", "doctor", "--fix"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Doctor {
flags: DoctorFlags {
fix: true,
yes: false,
fix_skills: false,
..
}
}
));
let cli = Cli::try_parse_from(["trusty-mpm", "doctor", "--fix", "--yes"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Doctor {
flags: DoctorFlags {
fix: true,
yes: true,
..
}
}
));
assert!(
Cli::try_parse_from(["trusty-mpm", "doctor", "--yes"]).is_err(),
"--yes without --fix must be rejected"
);
let cli = Cli::try_parse_from(["trusty-mpm", "doctor", "--fix", "--include-frozen"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Doctor {
flags: DoctorFlags {
fix: true,
include_frozen: true,
..
}
}
));
}
#[test]
fn cli_parses_validate() {
let cli = Cli::try_parse_from(["trusty-mpm", "validate"]).unwrap();
match cli.command.unwrap() {
Command::Validate { path, repair } => {
assert_eq!(path, None);
assert!(!repair);
}
other => panic!("expected Command::Validate, got {other:?}"),
}
}
#[test]
fn cli_parses_validate_with_path_and_repair() {
let cli =
Cli::try_parse_from(["trusty-mpm", "validate", "--path", "/tmp/ws", "--repair"]).unwrap();
match cli.command.unwrap() {
Command::Validate { path, repair } => {
assert_eq!(path, Some(std::path::PathBuf::from("/tmp/ws")));
assert!(repair);
}
other => panic!("expected Command::Validate, got {other:?}"),
}
}
#[test]
fn cli_parses_health() {
let cli = Cli::try_parse_from(["trusty-mpm", "health"]).unwrap();
assert!(matches!(cli.command.unwrap(), Command::Health));
}
#[test]
fn cli_parses_agent_list() {
let cli = Cli::try_parse_from(["trusty-mpm", "agent", "list"]).unwrap();
match cli.command.unwrap() {
Command::Agent {
action: AgentAction::List { json },
} => assert!(!json),
other => panic!("expected Command::Agent list, got {other:?}"),
}
}
#[test]
fn cli_parses_agent_list_json() {
let cli = Cli::try_parse_from(["trusty-mpm", "agent", "list", "--json"]).unwrap();
match cli.command.unwrap() {
Command::Agent {
action: AgentAction::List { json },
} => assert!(json),
other => panic!("expected Command::Agent list --json, got {other:?}"),
}
}
#[test]
fn cli_parses_agent_show() {
let cli = Cli::try_parse_from(["trusty-mpm", "agent", "show", "code-critic"]).unwrap();
match cli.command.unwrap() {
Command::Agent {
action: AgentAction::Show { name, json },
} => {
assert_eq!(name, "code-critic");
assert!(!json);
}
other => panic!("expected Command::Agent show, got {other:?}"),
}
}
#[test]
fn cli_parses_project_init() {
let cli = Cli::try_parse_from(["trusty-mpm", "project", "init"]).unwrap();
match cli.command.unwrap() {
Command::Project {
action: ProjectAction::Init { dir },
} => assert_eq!(dir, None),
other => panic!("expected project init, got {other:?}"),
}
}
#[test]
fn cli_parses_project_init_with_dir() {
let cli = Cli::try_parse_from(["trusty-mpm", "project", "init", "--dir", "/work/p"]).unwrap();
match cli.command.unwrap() {
Command::Project {
action: ProjectAction::Init { dir },
} => assert_eq!(dir.as_deref(), Some("/work/p")),
other => panic!("expected project init, got {other:?}"),
}
}
#[test]
fn cli_parses_project_list() {
let cli = Cli::try_parse_from(["trusty-mpm", "project", "list"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Project {
action: ProjectAction::List
}
));
}
#[test]
fn cli_parses_project_info() {
let cli = Cli::try_parse_from(["trusty-mpm", "project", "info"]).unwrap();
match cli.command.unwrap() {
Command::Project {
action: ProjectAction::Info { dir },
} => assert_eq!(dir, None),
other => panic!("expected project info, got {other:?}"),
}
}
#[test]
fn cli_project_requires_action() {
assert!(Cli::try_parse_from(["trusty-mpm", "project"]).is_err());
}
#[test]
fn cli_parses_meta_run() {
let cli = Cli::try_parse_from(["trusty-mpm", "meta", "run"]).unwrap();
match cli.command.unwrap() {
Command::Meta {
action:
MetaAction::Run {
demo,
project,
no_provision,
timeout_secs,
},
} => {
assert!(!demo);
assert_eq!(project, None);
assert!(!no_provision);
assert_eq!(timeout_secs, None);
}
other => panic!("expected meta run, got {other:?}"),
}
}
#[test]
fn cli_parses_meta_run_demo() {
let cli = Cli::try_parse_from(["trusty-mpm", "meta", "run", "--demo"]).unwrap();
match cli.command.unwrap() {
Command::Meta {
action: MetaAction::Run { demo, project, .. },
} => {
assert!(demo);
assert_eq!(project, None);
}
other => panic!("expected meta run --demo, got {other:?}"),
}
}
#[test]
fn cli_parses_meta_run_project() {
let cli =
Cli::try_parse_from(["trusty-mpm", "meta", "run", "--demo", "--project", "/tmp"]).unwrap();
match cli.command.unwrap() {
Command::Meta {
action: MetaAction::Run { demo, project, .. },
} => {
assert!(demo);
assert_eq!(project.as_deref(), Some(std::path::Path::new("/tmp")));
}
other => panic!("expected meta run --project, got {other:?}"),
}
}
#[test]
fn cli_parses_meta_run_no_provision() {
let cli = Cli::try_parse_from(["trusty-mpm", "meta", "run", "--no-provision"]).unwrap();
match cli.command.unwrap() {
Command::Meta {
action: MetaAction::Run { no_provision, .. },
} => assert!(no_provision),
other => panic!("expected meta run --no-provision, got {other:?}"),
}
}
#[test]
fn cli_parses_meta_run_timeout() {
let cli = Cli::try_parse_from(["trusty-mpm", "meta", "run", "--timeout-secs", "30"]).unwrap();
match cli.command.unwrap() {
Command::Meta {
action: MetaAction::Run { timeout_secs, .. },
} => assert_eq!(timeout_secs, Some(30)),
other => panic!("expected meta run --timeout-secs, got {other:?}"),
}
}
#[test]
fn cli_meta_requires_action() {
assert!(Cli::try_parse_from(["trusty-mpm", "meta"]).is_err());
}
#[test]
fn cli_parses_launch() {
let cli = Cli::try_parse_from(["trusty-mpm", "launch"]).unwrap();
match cli.command.unwrap() {
Command::Launch {
dir,
style,
worktree,
..
} => {
assert_eq!(dir, None);
assert_eq!(style, None);
assert!(!worktree, "#5274: a bare `tm launch` requests no worktree");
}
other => panic!("expected launch, got {other:?}"),
}
}
#[test]
fn cli_parses_launch_with_dir() {
let cli = Cli::try_parse_from(["trusty-mpm", "launch", "/work/p"]).unwrap();
match cli.command.unwrap() {
Command::Launch {
dir,
style,
worktree,
..
} => {
assert_eq!(dir.as_deref(), Some("/work/p"));
assert_eq!(style, None);
assert!(!worktree);
}
other => panic!("expected launch, got {other:?}"),
}
}
#[test]
fn cli_parses_launch_with_style() {
let cli =
Cli::try_parse_from(["trusty-mpm", "launch", "--style", "trusty-mpm-teacher"]).unwrap();
match cli.command.unwrap() {
Command::Launch {
dir,
style,
worktree,
..
} => {
assert_eq!(dir, None);
assert_eq!(style.as_deref(), Some("trusty-mpm-teacher"));
assert!(!worktree);
}
other => panic!("expected launch, got {other:?}"),
}
}
#[test]
fn cli_parses_launch_with_worktree() {
let cli = Cli::try_parse_from(["trusty-mpm", "launch", "--worktree"]).unwrap();
match cli.command.unwrap() {
Command::Launch {
dir,
style,
worktree,
..
} => {
assert_eq!(dir, None);
assert_eq!(style, None);
assert!(
worktree,
"`--worktree` must surface as an explicit worktree request (#5274)"
);
}
other => panic!("expected launch, got {other:?}"),
}
}
#[test]
fn cli_parses_launch_with_twin() {
for (argv, want) in [
(&["trusty-mpm", "launch", "--twin"][..], true),
(&["trusty-mpm", "launch"][..], false),
] {
match Cli::try_parse_from(argv).unwrap().command.unwrap() {
Command::Launch { twin, worktree, .. } => {
assert_eq!(twin, want, "{argv:?}");
assert!(!worktree, "{argv:?}");
}
other => panic!("expected launch, got {other:?}"),
}
}
}
#[test]
fn twin_preflight_refuses_only_twin_with_worktree() {
use crate::commands::launch_twin::preflight;
for (twin, worktree, refused) in [
(false, false, false),
(false, true, false),
(true, false, false),
(true, true, true),
] {
let got = preflight(twin, worktree);
assert_eq!(got.is_err(), refused, "twin={twin} worktree={worktree}");
}
}
#[tokio::test]
async fn a_twin_worktree_launch_is_refused_before_anything_is_touched() {
let root = tempfile::TempDir::new().expect("tempdir");
let absent = root.path().join("never-created");
let err = crate::commands::launch::launch(
&reqwest::Client::new(),
"http://127.0.0.1:1",
Some(absent.to_string_lossy().into_owned()),
None,
true,
crate::commands::managed_workspace::LaunchDir::OperatorCwd,
None,
true,
)
.await
.expect_err("a twin worktree launch is refused");
assert!(err.to_string().contains("`--worktree`"), "{err}");
assert!(!absent.exists());
}
#[test]
fn twin_placement_must_be_the_checked_directory() {
use crate::commands::launch_twin::confirm_placement;
let root = tempfile::TempDir::new().expect("tempdir");
let (checked, other) = (root.path().join("checked"), root.path().join("other"));
std::fs::create_dir(&checked).expect("mkdir");
std::fs::create_dir(&other).expect("mkdir");
assert!(confirm_placement(&checked, &checked.join(".")).is_ok());
assert!(confirm_placement(&checked, &other).is_err());
assert!(confirm_placement(&checked, &root.path().join("absent")).is_err());
}
#[test]
fn twin_refusals_apply_only_to_twin_launches() {
use crate::commands::launch_twin::{refuse_live_checkout, refuse_reattach};
for twin in [false, true] {
assert_eq!(
refuse_live_checkout(twin).is_err(),
twin,
"live, twin={twin}"
);
assert_eq!(
refuse_reattach(twin, "tm-x").is_err(),
twin,
"reattach, twin={twin}"
);
}
}
#[test]
fn fallback_session_name_has_tm_prefix() {
let name = fallback_session_name(std::path::Path::new("/work/p"));
assert!(name.starts_with("tm-"), "got {name}");
}
#[test]
fn fallback_session_name_uses_folder() {
let name = fallback_session_name(std::path::Path::new("/Users/x/trusty-mpm"));
assert_eq!(name, "tm-trusty-mpm");
}
#[test]
fn launch_banner_does_not_panic() {
print_launch_banner("/Users/test/project", "tmpm-quiet-falcon", None, None);
print_launch_banner(
"/Users/test/project",
"tmpm-quiet-falcon",
Some(std::path::Path::new("/tmp/system-prompt.txt")),
None,
);
}
#[test]
fn launch_reconnect_banner_does_not_panic() {
print_launch_banner_reconnecting("/Users/test/project", "tmpm-quiet-falcon");
}
#[test]
fn terminal_width_is_positive() {
assert!(terminal_width() > 0);
}
#[test]
fn banner_title_bar_contains_version() {
use crate::formatters::banner::two_panel::{render_two_panel_banner, strip_ansi};
use crate::formatters::info_box::{ConsoleInfo, DaemonInfo, WelcomeData};
colored::control::set_override(false);
let data = WelcomeData {
project: "my-project".to_string(),
workspace: "/Users/test/project".to_string(),
user: "operator".to_string(),
reconnecting: false,
session_name: "tmpm-my-project".to_string(),
daemon: DaemonInfo::default(),
console: ConsoleInfo::default(),
recent_commits: vec![],
memory_status: "(not detected)".to_string(),
search_status: "(not detected)".to_string(),
review_status: "(not detected)".to_string(),
};
let out = render_two_panel_banner(&data, 120, false).expect("wide banner");
let first_line = out.lines().next().unwrap_or("");
let bare = strip_ansi(first_line);
assert!(
bare.contains(env!("CARGO_PKG_VERSION")),
"title bar must contain CARGO_PKG_VERSION: {bare:?}"
);
assert!(
bare.starts_with('β'),
"title bar must start with outer box corner β: {bare:?}"
);
colored::control::unset_override();
}
#[test]
fn banner_reconnect_label_in_wide_mode() {
use crate::formatters::banner::two_panel::{render_two_panel_banner, strip_ansi};
use crate::formatters::info_box::{ConsoleInfo, DaemonInfo, WelcomeData};
colored::control::set_override(false);
let data = WelcomeData {
project: "my-project".to_string(),
workspace: "/Users/test/project".to_string(),
user: "operator".to_string(),
reconnecting: true,
session_name: "tmpm-quiet-falcon".to_string(),
daemon: DaemonInfo::default(),
console: ConsoleInfo::default(),
recent_commits: vec![],
memory_status: "(not detected)".to_string(),
search_status: "(not detected)".to_string(),
review_status: "(not detected)".to_string(),
};
let out = render_two_panel_banner(&data, 120, true).expect("wide reconnect banner");
let bare = strip_ansi(&out);
assert!(
bare.contains("Reconnecting..."),
"wide reconnect banner must contain 'Reconnecting...': {bare:.200}"
);
colored::control::unset_override();
}
#[test]
fn normalize_workdir_strips_trailing_slash() {
let with = normalize_workdir("/no/such/dir/");
let without = normalize_workdir("/no/such/dir");
assert_eq!(with, without);
assert_eq!(without, "/no/such/dir");
}
#[test]
fn cli_parses_session_start() {
let cli = Cli::try_parse_from(["trusty-mpm", "session", "start"]).unwrap();
match cli.command.unwrap() {
Command::Session {
action: SessionAction::Start { dir },
} => assert_eq!(dir, None),
other => panic!("expected session start, got {other:?}"),
}
}
#[test]
fn cli_parses_session_start_with_dir() {
let cli = Cli::try_parse_from(["trusty-mpm", "session", "start", "--dir", "/work/p"]).unwrap();
match cli.command.unwrap() {
Command::Session {
action: SessionAction::Start { dir },
} => assert_eq!(dir.as_deref(), Some("/work/p")),
other => panic!("expected session start, got {other:?}"),
}
}
#[test]
fn cli_parses_session_stop() {
let cli = Cli::try_parse_from(["trusty-mpm", "session", "stop", "tmpm-quiet-falcon"]).unwrap();
match cli.command.unwrap() {
Command::Session {
action: SessionAction::Stop { id_or_name },
} => assert_eq!(id_or_name, "tmpm-quiet-falcon"),
other => panic!("expected session stop, got {other:?}"),
}
}
#[test]
fn cli_session_stop_requires_arg() {
assert!(Cli::try_parse_from(["trusty-mpm", "session", "stop"]).is_err());
}
#[test]
fn cli_parses_session_kill() {
let cli = Cli::try_parse_from(["trusty-mpm", "session", "kill", "tmpm-quiet-falcon"]).unwrap();
match cli.command.unwrap() {
Command::Session {
action: SessionAction::Stop { id_or_name },
} => assert_eq!(id_or_name, "tmpm-quiet-falcon"),
other => panic!("expected session kill (stop), got {other:?}"),
}
}
#[test]
fn cli_parses_session_list() {
let cli = Cli::try_parse_from(["trusty-mpm", "session", "list"]).unwrap();
match cli.command.unwrap() {
Command::Session {
action: SessionAction::List { dir },
} => assert_eq!(dir, None),
other => panic!("expected session list, got {other:?}"),
}
}
#[test]
fn cli_parses_session_clean() {
let cli = Cli::try_parse_from(["trusty-mpm", "session", "clean"]).unwrap();
match cli.command.unwrap() {
Command::Session {
action: SessionAction::Clean { dir },
} => assert_eq!(dir, None),
other => panic!("expected session clean, got {other:?}"),
}
}
#[test]
fn cli_parses_session_info() {
let cli = Cli::try_parse_from(["trusty-mpm", "session", "info", "abc-123"]).unwrap();
match cli.command.unwrap() {
Command::Session {
action: SessionAction::Info { id_or_name },
} => assert_eq!(id_or_name, "abc-123"),
other => panic!("expected session info, got {other:?}"),
}
}
#[test]
fn cli_parses_session_instructions() {
let cli = Cli::try_parse_from(["trusty-mpm", "session", "instructions"]).unwrap();
match cli.command.unwrap() {
Command::Session {
action: SessionAction::Instructions { dir },
} => assert_eq!(dir, None),
other => panic!("expected session instructions, got {other:?}"),
}
}
#[test]
fn cli_parses_session_instructions_with_dir() {
let cli =
Cli::try_parse_from(["trusty-mpm", "session", "instructions", "--dir", "/tmp"]).unwrap();
match cli.command.unwrap() {
Command::Session {
action: SessionAction::Instructions { dir },
} => assert_eq!(dir.as_deref(), Some("/tmp")),
other => panic!("expected session instructions, got {other:?}"),
}
}
#[test]
fn cli_parses_events() {
let cli = Cli::try_parse_from(["trusty-mpm", "events"]).unwrap();
assert!(matches!(cli.command.unwrap(), Command::Events));
}
#[test]
fn cli_url_flag_overrides_default() {
let cli = Cli::try_parse_from(["trusty-mpm", "--url", "http://x:9", "status"]).unwrap();
assert_eq!(cli.url.as_deref(), Some("http://x:9"));
}
#[test]
fn cli_url_flag_equal_to_default_is_still_some() {
let cli = Cli::try_parse_from(["trusty-mpm", "--url", DEFAULT_URL, "status"]).unwrap();
assert_eq!(
cli.url.as_deref(),
Some(DEFAULT_URL),
"an explicit --url equal to the default must still parse as Some, not be lost"
);
}
#[test]
fn cli_parses_account_flag_global() {
let cli = Cli::try_parse_from(["trusty-mpm", "--account", "bob-duetto", "status"]).unwrap();
assert_eq!(cli.account.as_deref(), Some("bob-duetto"));
}
#[test]
fn cli_account_flag_after_subcommand() {
let cli = Cli::try_parse_from([
"trusty-mpm",
"run",
"--account",
"bob-duetto",
"bobmatnyc/trusty-tools",
])
.unwrap();
assert_eq!(cli.account.as_deref(), Some("bob-duetto"));
assert!(matches!(cli.command, Some(Command::Run { .. })));
}
#[test]
fn cli_parses_user_alias_for_account_global() {
let cli = Cli::try_parse_from(["trusty-mpm", "--user", "bob-duetto", "status"]).unwrap();
assert_eq!(cli.account.as_deref(), Some("bob-duetto"));
}
#[test]
fn cli_parses_u_alias_for_account_global() {
let cli = Cli::try_parse_from(["trusty-mpm", "--u", "bob-duetto", "status"]).unwrap();
assert_eq!(cli.account.as_deref(), Some("bob-duetto"));
}
#[test]
fn cli_rejects_a_blank_account_flag_before_the_repository() {
for argv in [
&["trusty-mpm", "--user=", "acme/widget"][..],
&["trusty-mpm", "--account=", "acme/widget"][..],
&["trusty-mpm", "--user", "", "acme/widget"][..],
&["trusty-mpm", "--account", " ", "acme/widget"][..],
&["trusty-mpm", "--user= ", "run", "acme/widget"][..],
] {
let err = Cli::try_parse_from(argv)
.expect_err("a blank account flag must refuse, not fall back to the global account");
assert!(
err.to_string().contains("needs a gh login"),
"{argv:?}: {err}"
);
}
}
#[test]
fn cli_user_alias_after_subcommand() {
let cli = Cli::try_parse_from([
"trusty-mpm",
"run",
"https://github.com/duettoresearch/jev-matching",
"--user",
"bob-duetto",
])
.unwrap();
assert_eq!(cli.account.as_deref(), Some("bob-duetto"));
assert!(matches!(cli.command, Some(Command::Run { .. })));
}
#[test]
fn cli_bare_invocation_uses_guided_default() {
let cli = Cli::try_parse_from(["trusty-mpm"]).expect("bare tm must parse");
assert!(
cli.command.is_none(),
"bare tm should produce command = None"
);
}
#[test]
fn cli_parses_tui_defaults() {
let cli = Cli::try_parse_from(["trusty-mpm", "tui", "--url", DEFAULT_URL]).unwrap();
match cli.command.unwrap() {
Command::Tui {
url,
interval_ms,
single_pane,
} => {
assert_eq!(url.as_deref(), Some(DEFAULT_URL));
assert_eq!(interval_ms, 1000);
assert!(!single_pane);
assert_eq!(
trusty_mpm::tui::initial_view(single_pane),
trusty_mpm::tui::TuiView::Multipane
);
}
other => panic!("expected Tui, got {other:?}"),
}
}
#[test]
fn cli_parses_tui_single_pane_opt_in() {
let cli = Cli::try_parse_from(["trusty-mpm", "tui", "--single-pane"]).unwrap();
match cli.command.unwrap() {
Command::Tui { single_pane, .. } => {
assert!(single_pane);
assert_eq!(
trusty_mpm::tui::initial_view(single_pane),
trusty_mpm::tui::TuiView::SinglePane
);
}
other => panic!("expected Tui, got {other:?}"),
}
}
#[test]
fn cli_parses_tui_with_interval() {
let cli = Cli::try_parse_from(["trusty-mpm", "tui", "--interval-ms", "500"]).unwrap();
match cli.command.unwrap() {
Command::Tui { interval_ms, .. } => assert_eq!(interval_ms, 500),
other => panic!("expected Tui, got {other:?}"),
}
}
#[test]
fn cli_parses_session_tui() {
let cli = Cli::try_parse_from(["trusty-mpm", "session", "tui"]).unwrap();
match cli.command.unwrap() {
Command::Session {
action: SessionAction::Tui { interval_ms, .. },
} => {
assert_eq!(interval_ms, 1500);
}
other => panic!("expected Session::Tui, got {other:?}"),
}
}
#[test]
fn cli_parses_session_tui_with_flags() {
let cli = Cli::try_parse_from([
"trusty-mpm",
"session",
"tui",
"--url",
"http://127.0.0.1:9999",
"--interval-ms",
"750",
])
.unwrap();
match cli.command.unwrap() {
Command::Session {
action: SessionAction::Tui { url, interval_ms },
} => {
assert_eq!(url.as_deref(), Some("http://127.0.0.1:9999"));
assert_eq!(interval_ms, 750);
}
other => panic!("expected Session::Tui, got {other:?}"),
}
}
#[test]
fn cli_parses_session_singular() {
let cli = Cli::try_parse_from(["trusty-mpm", "session", "list"]).unwrap();
match cli.command.unwrap() {
Command::Session {
action: SessionAction::List { dir },
} => assert_eq!(dir, None),
other => panic!("expected session list, got {other:?}"),
}
}
#[test]
fn cli_rejects_removed_coordinator_tui() {
let cli = Cli::try_parse_from(["trusty-mpm", "coordinator-tui"]).unwrap();
match cli.command.unwrap() {
Command::External(tokens) => assert_eq!(tokens, ["coordinator-tui"]),
other => panic!("expected the External catch-all, got {other:?}"),
}
assert!(
crate::commands::run_target::classify_bare("coordinator-tui").is_none(),
"a retired subcommand must route to the usage error, never to a managed run"
);
}
#[test]
fn cli_parses_telegram_pair() {
let cli = Cli::try_parse_from(["trusty-mpm", "telegram", "pair"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Telegram {
cmd: TelegramCmd::Pair
}
));
}
#[test]
fn cli_parses_telegram_status() {
let cli = Cli::try_parse_from(["trusty-mpm", "telegram", "status"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Telegram {
cmd: TelegramCmd::Status
}
));
}
#[test]
fn cli_parses_telegram_stop() {
let cli = Cli::try_parse_from(["trusty-mpm", "telegram", "stop"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Telegram {
cmd: TelegramCmd::Stop
}
));
}
#[test]
fn cli_telegram_requires_subcommand() {
assert!(Cli::try_parse_from(["trusty-mpm", "telegram"]).is_err());
}
#[test]
fn cli_parses_telegram_start_with_check() {
let cli = Cli::try_parse_from(["trusty-mpm", "telegram", "start", "--check"]).unwrap();
match cli.command.unwrap() {
Command::Telegram {
cmd: TelegramCmd::Start { check, token, .. },
} => {
assert!(check);
assert_eq!(token, None);
}
other => panic!("expected Telegram start, got {other:?}"),
}
}
#[test]
fn cli_parses_telegram_start_with_token() {
let cli =
Cli::try_parse_from(["trusty-mpm", "telegram", "start", "--token", "secret"]).unwrap();
match cli.command.unwrap() {
Command::Telegram {
cmd: TelegramCmd::Start { token, check, .. },
} => {
assert_eq!(token.as_deref(), Some("secret"));
assert!(!check);
}
other => panic!("expected Telegram start, got {other:?}"),
}
}
#[test]
fn cli_parses_telegram_start_alert_and_user_flags() {
let cli = Cli::try_parse_from([
"trusty-mpm",
"telegram",
"start",
"--alert-chat-id",
"12345",
"--allowed-user-id",
"67890",
])
.unwrap();
match cli.command.unwrap() {
Command::Telegram {
cmd:
TelegramCmd::Start {
alert_chat_id,
allowed_user_id,
..
},
} => {
assert_eq!(alert_chat_id, Some(12345));
assert_eq!(allowed_user_id, Some(67890));
}
other => panic!("expected Telegram start, got {other:?}"),
}
}
#[test]
fn cli_parses_slack_stop() {
let cli = Cli::try_parse_from(["trusty-mpm", "slack", "stop"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Slack {
cmd: SlackCmd::Stop
}
));
}
#[test]
fn cli_slack_requires_subcommand() {
assert!(Cli::try_parse_from(["trusty-mpm", "slack"]).is_err());
}
#[test]
fn cli_parses_slack_start_with_check() {
let cli = Cli::try_parse_from(["trusty-mpm", "slack", "start", "--check"]).unwrap();
match cli.command.unwrap() {
Command::Slack {
cmd:
SlackCmd::Start {
check,
bot_token,
app_token,
..
},
} => {
assert!(check);
assert_eq!(bot_token, None);
assert_eq!(app_token, None);
}
other => panic!("expected Slack start, got {other:?}"),
}
}
#[test]
fn cli_parses_slack_start_with_tokens() {
let cli = Cli::try_parse_from([
"trusty-mpm",
"slack",
"start",
"--bot-token",
"xoxb-secret",
"--app-token",
"xapp-secret",
])
.unwrap();
match cli.command.unwrap() {
Command::Slack {
cmd:
SlackCmd::Start {
bot_token,
app_token,
check,
..
},
} => {
assert_eq!(bot_token.as_deref(), Some("xoxb-secret"));
assert_eq!(app_token.as_deref(), Some("xapp-secret"));
assert!(!check);
}
other => panic!("expected Slack start, got {other:?}"),
}
}
#[test]
fn cli_parses_daemon_defaults() {
let cli = Cli::try_parse_from(["trusty-mpm", "daemon"]).unwrap();
match cli.command.unwrap() {
Command::Daemon {
addr,
tailscale,
mcp,
force,
sandbox,
} => {
assert_eq!(addr.to_string(), "127.0.0.1:7880");
assert!(!tailscale);
assert!(!mcp);
assert!(!force);
assert!(!sandbox);
}
other => panic!("expected Daemon, got {other:?}"),
}
}
#[test]
fn cli_parses_daemon_sandbox() {
let cli = Cli::try_parse_from(["trusty-mpm", "daemon", "--sandbox"]).unwrap();
match cli.command.unwrap() {
Command::Daemon { sandbox, .. } => assert!(sandbox),
other => panic!("expected Daemon, got {other:?}"),
}
}
#[test]
fn cli_parses_daemon_tailscale() {
let cli = Cli::try_parse_from(["trusty-mpm", "daemon", "--tailscale"]).unwrap();
match cli.command.unwrap() {
Command::Daemon { tailscale, .. } => assert!(tailscale),
other => panic!("expected Daemon, got {other:?}"),
}
}
#[test]
fn cli_parses_daemon_force() {
let cli = Cli::try_parse_from(["trusty-mpm", "daemon", "--force"]).unwrap();
match cli.command.unwrap() {
Command::Daemon { force, .. } => assert!(force),
other => panic!("expected Daemon, got {other:?}"),
}
}
#[test]
fn cli_infers_abbreviated_subcommands() {
let cli = Cli::try_parse_from(["trusty-mpm", "doc"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Doctor {
flags: DoctorFlags {
prune_stale_skills: false,
..
}
}
));
let cli = Cli::try_parse_from(["trusty-mpm", "heal"]).unwrap();
assert!(matches!(cli.command.unwrap(), Command::Health));
let cli = Cli::try_parse_from(["trusty-mpm", "rest"]).unwrap();
assert!(matches!(cli.command.unwrap(), Command::Restart));
let cli = Cli::try_parse_from(["trusty-mpm", "gen", "cap"]).unwrap();
assert!(matches!(cli.command.unwrap(), Command::Generate { .. }));
let cli = Cli::try_parse_from(["trusty-mpm", "project", "ini"]).unwrap();
match cli.command.unwrap() {
Command::Project {
action: ProjectAction::Init { dir },
} => assert_eq!(dir, None),
other => panic!("expected Command::Project(Init), got {other:?}"),
}
}
#[test]
fn cli_exact_match_wins_over_prefix_ambiguity() {
let cli = Cli::try_parse_from(["trusty-mpm", "status"]).unwrap();
assert!(matches!(cli.command.unwrap(), Command::Status));
let cli = Cli::try_parse_from(["trusty-mpm", "statusline"]).unwrap();
assert!(matches!(cli.command.unwrap(), Command::Statusline));
let cli = Cli::try_parse_from(["trusty-mpm", "hook"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Hook {
pm_guard: false,
divert_check: false,
prompt_feedback: false
}
));
let cli = Cli::try_parse_from(["trusty-mpm", "hooks", "clean"]).unwrap();
assert!(matches!(cli.command.unwrap(), Command::Hooks { .. }));
let cli = Cli::try_parse_from(["trusty-mpm", "project", "list"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Project {
action: ProjectAction::List
}
));
let cli = Cli::try_parse_from(["trusty-mpm", "projects", "list"]).unwrap();
assert!(matches!(cli.command.unwrap(), Command::Projects { .. }));
let cli = Cli::try_parse_from(["trusty-mpm", "session", "start"]).unwrap();
assert!(matches!(cli.command.unwrap(), Command::Session { .. }));
let cli = Cli::try_parse_from(["trusty-mpm", "sessions", "start"]).unwrap();
assert!(matches!(cli.command.unwrap(), Command::Sessions { .. }));
}
#[test]
fn cli_infers_internal_spawn_disclaimed_but_runtime_guard_rejects_abbreviations() {
for prefix in ["int", "inte", "internal"] {
let raw_argv: Vec<String> = vec!["trusty-mpm".into(), prefix.into(), "claude".into()];
let cli = Cli::try_parse_from(raw_argv.clone()).unwrap();
assert!(
matches!(cli.command, Some(Command::InternalSpawnDisclaimed { .. })),
"expected clap to still infer {prefix:?} to InternalSpawnDisclaimed"
);
assert!(
!crate::commands::spawn_disclaimed::invoked_literally(&raw_argv),
"the runtime guard must reject the abbreviated prefix {prefix:?}"
);
}
let raw_argv: Vec<String> = vec![
"trusty-mpm".into(),
"internal-spawn-disclaimed".into(),
"claude".into(),
];
let cli = Cli::try_parse_from(raw_argv.clone()).unwrap();
assert!(matches!(
cli.command,
Some(Command::InternalSpawnDisclaimed { .. })
));
assert!(crate::commands::spawn_disclaimed::invoked_literally(
&raw_argv
));
}
#[test]
fn cli_infers_ins_to_install_unaffected_by_disclaim_guard() {
let cli = Cli::try_parse_from(["trusty-mpm", "ins"]).unwrap();
assert!(matches!(cli.command.unwrap(), Command::Install { .. }));
}
#[test]
fn cli_parses_supervisor() {
let cli = Cli::try_parse_from(["trusty-mpm", "supervisor"]).unwrap();
match cli.command.unwrap() {
Command::Supervisor {
interval,
auto_resume,
no_classify,
..
} => {
assert_eq!(interval, None);
assert!(!auto_resume);
assert!(!no_classify);
}
other => panic!("expected Supervisor, got {other:?}"),
}
}
#[test]
fn cli_parses_supervisor_flags() {
let cli = Cli::try_parse_from([
"trusty-mpm",
"supervisor",
"--interval",
"60",
"--auto-resume",
"--no-classify",
])
.unwrap();
match cli.command.unwrap() {
Command::Supervisor {
interval,
auto_resume,
no_classify,
..
} => {
assert_eq!(interval, Some(60));
assert!(auto_resume);
assert!(no_classify);
}
other => panic!("expected Supervisor, got {other:?}"),
}
}
#[test]
fn deprecation_notice_format() {
use crate::commands::managed::deprecation_message;
assert_eq!(
deprecation_message("runtime-stop", "stop"),
"warning: 'runtime-stop' is deprecated; use 'stop'"
);
assert_eq!(
deprecation_message("managed-resume", "resume"),
"warning: 'managed-resume' is deprecated; use 'resume'"
);
}
#[test]
fn resolve_managed_target_matches_by_id() {
use trusty_mpm::client::{ManagedSessionSummary, resolve_target};
let uuid = "11111111-2222-3333-4444-555555555555";
let sessions: Vec<ManagedSessionSummary> = serde_json::from_value(serde_json::json!([
{ "id": uuid, "name": "tmpm-quiet-falcon", "state": "running" }
]))
.unwrap();
assert_eq!(
resolve_target(&sessions, uuid).map(|s| s.id.clone()),
Some(uuid.to_string()),
"a managed UUID must resolve to the managed id (routes to managed endpoint)"
);
}
#[test]
fn resolve_managed_target_matches_by_name_returns_id() {
use trusty_mpm::client::{ManagedSessionSummary, resolve_target};
let uuid = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
let sessions: Vec<ManagedSessionSummary> = serde_json::from_value(serde_json::json!([
{ "id": uuid, "name": "tmpm-brave-otter", "state": "stopped" }
]))
.unwrap();
assert_eq!(
resolve_target(&sessions, "tmpm-brave-otter").map(|s| s.id.clone()),
Some(uuid.to_string()),
"a managed name must resolve to its id, not be passed through verbatim"
);
}
#[test]
fn resolve_managed_target_unknown_falls_back_to_none() {
use trusty_mpm::client::{ManagedSessionSummary, resolve_target};
let sessions: Vec<ManagedSessionSummary> = serde_json::from_value(serde_json::json!([
{ "id": "11111111-2222-3333-4444-555555555555", "name": "tmpm-quiet-falcon", "state": "running" }
]))
.unwrap();
assert_eq!(
resolve_target(&sessions, "some-local-session-name").map(|s| s.id.clone()),
None,
"an unknown id/name must yield None so `stop`/`resume` fall back to project-sessions"
);
}
#[test]
fn resolve_managed_target_empty_list_is_none() {
use trusty_mpm::client::{ManagedSessionSummary, resolve_target};
let sessions: Vec<ManagedSessionSummary> = Vec::new();
assert_eq!(
resolve_target(&sessions, "anything").map(|s| s.id.clone()),
None
);
}
#[test]
fn cli_parses_content_install_update_and_status() {
use crate::cli::ContentAction;
let parse = |args: &[&str]| {
let mut argv = vec!["trusty-mpm", "content"];
argv.extend_from_slice(args);
match Cli::try_parse_from(argv).unwrap().command.unwrap() {
Command::Content { action } => action,
other => panic!("expected content, got {other:?}"),
}
};
match parse(&["install", "--from", "/b/content-v0.1.0.tar.gz"]) {
ContentAction::Install { from } => {
assert_eq!(from, std::path::Path::new("/b/content-v0.1.0.tar.gz"));
}
other => panic!("expected install, got {other:?}"),
}
assert!(matches!(
parse(&["update"]),
ContentAction::Update { content_ref: None }
));
match parse(&["update", "--content-ref", "content-v0.2.0"]) {
ContentAction::Update { content_ref } => {
assert_eq!(content_ref.as_deref(), Some("content-v0.2.0"));
}
other => panic!("expected update, got {other:?}"),
}
assert!(matches!(parse(&["status"]), ContentAction::Status));
assert!(Cli::try_parse_from(["trusty-mpm", "content", "install"]).is_err());
}
#[test]
fn cli_content_help_states_the_sidecar_limit() {
use clap::CommandFactory as _;
for verb in ["install", "update"] {
let mut root = Cli::command();
let help = root
.find_subcommand_mut("content")
.expect("content group")
.find_subcommand_mut(verb)
.expect("content verb")
.render_long_help()
.to_string();
for needle in ["is required", "proves the transfer only", "first use"] {
assert!(
help.contains(needle),
"`content {verb} --help` omits {needle}"
);
}
}
}
#[test]
fn cli_parses_catalog_sync() {
let cli = Cli::try_parse_from(["trusty-mpm", "catalog", "sync", "--force"]).unwrap();
match cli.command.unwrap() {
Command::Catalog {
action: CatalogAction::Sync { force },
} => assert!(force),
other => panic!("expected catalog sync, got {other:?}"),
}
}
#[test]
fn cli_parses_catalog_ls() {
let cli = Cli::try_parse_from(["trusty-mpm", "catalog", "ls"]).unwrap();
match cli.command.unwrap() {
Command::Catalog {
action: CatalogAction::Ls { json },
} => assert!(!json),
other => panic!("expected catalog ls, got {other:?}"),
}
}
#[test]
fn cli_parses_catalog_status() {
let cli = Cli::try_parse_from(["trusty-mpm", "catalog", "status", "--json"]).unwrap();
match cli.command.unwrap() {
Command::Catalog {
action: CatalogAction::Status { json },
} => assert!(json),
other => panic!("expected catalog status, got {other:?}"),
}
}
#[test]
fn cli_parses_catalog_apply() {
let cli =
Cli::try_parse_from(["trusty-mpm", "catalog", "apply", "--force", "--prune"]).unwrap();
match cli.command.unwrap() {
Command::Catalog {
action: CatalogAction::Apply { force, prune },
} => {
assert!(force);
assert!(prune);
}
other => panic!("expected catalog apply, got {other:?}"),
}
}
#[test]
fn cli_parses_catalog_apply_defaults() {
let cli = Cli::try_parse_from(["trusty-mpm", "catalog", "apply"]).unwrap();
match cli.command.unwrap() {
Command::Catalog {
action: CatalogAction::Apply { force, prune },
} => {
assert!(!force);
assert!(!prune);
}
other => panic!("expected catalog apply, got {other:?}"),
}
}
#[test]
fn cli_parses_ticket() {
use crate::commands::ticket::system::TicketSystemKind;
let cli = Cli::try_parse_from(["trusty-mpm", "ticket", "1232"]).unwrap();
match cli.command.unwrap() {
Command::Ticket {
issue,
system,
notes,
runtime,
} => {
assert_eq!(issue, "1232");
assert_eq!(system, TicketSystemKind::Gh);
assert!(notes.is_empty());
assert_eq!(runtime, trusty_mpm::runtime::RuntimeKind::ClaudeCode);
}
other => panic!("expected ticket, got {other:?}"),
}
}
#[test]
fn cli_parses_ticket_with_hash_and_system() {
use crate::commands::ticket::system::TicketSystemKind;
let cli = Cli::try_parse_from(["trusty-mpm", "ticket", "#1232", "jira"]).unwrap();
match cli.command.unwrap() {
Command::Ticket { issue, system, .. } => {
assert_eq!(issue, "#1232");
assert_eq!(system, TicketSystemKind::Jira);
}
other => panic!("expected ticket, got {other:?}"),
}
}
#[test]
fn cli_parses_ticket_with_notes() {
let cli = Cli::try_parse_from([
"trusty-mpm",
"ticket",
"42",
"-m",
"starting work",
"--note",
"second note",
])
.unwrap();
match cli.command.unwrap() {
Command::Ticket { issue, notes, .. } => {
assert_eq!(issue, "42");
assert_eq!(notes, vec!["starting work", "second note"]);
}
other => panic!("expected ticket, got {other:?}"),
}
}
#[test]
fn cli_rejects_ticket_unknown_system() {
let err = Cli::try_parse_from(["trusty-mpm", "ticket", "1", "bogus"]);
assert!(err.is_err(), "expected unknown system to be rejected");
}
#[test]
fn cli_parses_issue_seed_labels() {
use crate::cli::IssueCmd;
let cli = Cli::try_parse_from(["trusty-mpm", "issue", "seed-labels", "--dry-run"]).unwrap();
match cli.command.unwrap() {
Command::Issue { cmd, .. } => match cmd {
IssueCmd::SeedLabels {
dry_run,
config,
only,
} => {
assert!(dry_run);
assert!(config.is_none());
assert!(only.is_empty(), "an unscoped run has no filters");
}
other => panic!("expected seed-labels, got {other:?}"),
},
other => panic!("expected issue, got {other:?}"),
}
}
#[test]
fn cli_parses_issue_seed_labels_only_filters_7983() {
use crate::cli::IssueCmd;
let cli = Cli::try_parse_from([
"trusty-mpm",
"issue",
"seed-labels",
"--only",
"status:",
"--only",
"trusty-mpm",
])
.unwrap();
match cli.command.unwrap() {
Command::Issue { cmd, .. } => match cmd {
IssueCmd::SeedLabels { only, dry_run, .. } => {
assert_eq!(only, vec!["status:".to_string(), "trusty-mpm".to_string()]);
assert!(!dry_run);
}
other => panic!("expected seed-labels, got {other:?}"),
},
other => panic!("expected issue, got {other:?}"),
}
}
#[test]
fn cli_parses_issue_transition() {
use crate::cli::IssueCmd;
let cli = Cli::try_parse_from([
"trusty-mpm",
"issue",
"transition",
"1232",
"approved",
"--note",
"approved by reviewer",
])
.unwrap();
match cli.command.unwrap() {
Command::Issue { cmd, .. } => match cmd {
IssueCmd::Transition {
issue,
to_state,
note,
..
} => {
assert_eq!(issue, 1232);
assert_eq!(to_state, "approved");
assert_eq!(note.as_deref(), Some("approved by reviewer"));
}
other => panic!("expected transition, got {other:?}"),
},
other => panic!("expected issue, got {other:?}"),
}
}
#[test]
fn cli_parses_issue_current_and_states_and_repair() {
use crate::cli::IssueCmd;
let current = Cli::try_parse_from(["trusty-mpm", "issue", "current", "7"]).unwrap();
assert!(matches!(
current.command.unwrap(),
Command::Issue {
cmd: IssueCmd::Current { issue: 7, .. },
..
}
));
let states = Cli::try_parse_from(["trusty-mpm", "issue", "states"]).unwrap();
assert!(matches!(
states.command.unwrap(),
Command::Issue {
cmd: IssueCmd::States { .. },
..
}
));
let repair = Cli::try_parse_from(["trusty-mpm", "issue", "repair", "9"]).unwrap();
assert!(matches!(
repair.command.unwrap(),
Command::Issue {
cmd: IssueCmd::Repair { issue: 9, .. },
..
}
));
}
#[test]
fn cli_parses_issue_seed_config_force() {
use crate::cli::IssueCmd;
let cli = Cli::try_parse_from(["trusty-mpm", "issue", "seed-config", "--force"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Issue {
cmd: IssueCmd::SeedConfig { force: true },
..
}
));
}
#[test]
fn cli_parses_issue_audit_single_and_window() {
use crate::cli::IssueCmd;
let cli = Cli::try_parse_from(["trusty-mpm", "issue", "audit", "7093"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Issue {
cmd: IssueCmd::Audit {
issue: Some(7093),
recent: None,
since: None
},
..
}
));
let cli = Cli::try_parse_from(["trusty-mpm", "issue", "audit", "--recent", "20"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Issue {
cmd: IssueCmd::Audit {
issue: None,
recent: Some(20),
since: None
},
..
}
));
let cli =
Cli::try_parse_from(["trusty-mpm", "issue", "audit", "--since", "2026-09-01"]).unwrap();
match cli.command.unwrap() {
Command::Issue {
cmd: IssueCmd::Audit { since, .. },
..
} => assert_eq!(since.as_deref(), Some("2026-09-01")),
other => panic!("expected issue audit --since, got {other:?}"),
}
assert!(
Cli::try_parse_from(["trusty-mpm", "issue", "audit", "7093", "--recent", "5"]).is_err(),
"an issue number and --recent must conflict"
);
assert!(
Cli::try_parse_from([
"trusty-mpm",
"issue",
"audit",
"--recent",
"5",
"--since",
"2026-09-01"
])
.is_err(),
"--recent and --since must conflict"
);
}
#[test]
fn cli_parses_issue_epic_create() {
use crate::cli::{EpicCmd, IssueCmd};
let cli = Cli::try_parse_from([
"trusty-mpm",
"issue",
"epic",
"create",
"--from",
"docs/research/tm-epic-cli/epic-plan.md",
"--milestone",
"Issue management",
"--component",
"trusty-mpm",
"--project",
"3",
"--dry-run",
])
.unwrap();
match cli.command.unwrap() {
Command::Issue {
cmd:
IssueCmd::Epic(EpicCmd::Create {
from,
milestone,
component,
phase_type,
project,
session,
tracker,
dry_run,
}),
..
} => {
assert_eq!(
from.to_string_lossy(),
"docs/research/tm-epic-cli/epic-plan.md"
);
assert_eq!(milestone.as_deref(), Some("Issue management"));
assert_eq!(component, vec!["trusty-mpm".to_string()]);
assert_eq!(phase_type, "enhancement", "the default type label");
assert_eq!(project, Some(3));
assert!(session.is_none());
assert!(tracker.is_none());
assert!(dry_run);
}
other => panic!("expected issue epic create, got {other:?}"),
}
}
#[test]
fn cli_parses_issue_epic_create_repeatable_components() {
use crate::cli::{EpicCmd, IssueCmd};
let cli = Cli::try_parse_from([
"trusty-mpm",
"issue",
"epic",
"create",
"--from",
"plan.md",
"--milestone",
"Backlog Β· mpm/core",
"--component",
"trusty-mpm",
"--component",
"trusty-common",
"--phase-type",
"refactor",
"--session",
"tm-trusty-tools-15",
"--tracker",
"8445",
])
.unwrap();
match cli.command.unwrap() {
Command::Issue {
cmd:
IssueCmd::Epic(EpicCmd::Create {
component,
phase_type,
session,
tracker,
dry_run,
..
}),
..
} => {
assert_eq!(
component,
vec!["trusty-mpm".to_string(), "trusty-common".to_string()]
);
assert_eq!(phase_type, "refactor");
assert_eq!(session.as_deref(), Some("tm-trusty-tools-15"));
assert_eq!(tracker, Some(8445));
assert!(!dry_run);
}
other => panic!("expected issue epic create, got {other:?}"),
}
assert!(
Cli::try_parse_from(["trusty-mpm", "issue", "epic", "create"]).is_err(),
"--from must be required"
);
}
#[test]
fn cli_parses_issue_epic_sync() {
use crate::cli::{EpicCmd, IssueCmd};
let cli = Cli::try_parse_from(["trusty-mpm", "issue", "epic", "sync", "8445"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Issue {
cmd: IssueCmd::Epic(EpicCmd::Sync { epic: 8445 }),
..
}
));
assert!(
Cli::try_parse_from(["trusty-mpm", "issue", "epic", "sync"]).is_err(),
"the tracker number must be required"
);
}
#[test]
fn cli_parses_issue_epic_defer() {
use crate::cli::{EpicCmd, IssueCmd};
let cli = Cli::try_parse_from([
"trusty-mpm",
"issue",
"epic",
"defer",
"8445",
"--item",
"the gap",
"--why",
"later",
"--where",
"unscheduled",
])
.unwrap();
match cli.command.unwrap() {
Command::Issue {
cmd:
IssueCmd::Epic(EpicCmd::Defer {
epic,
item,
why,
destination,
}),
..
} => {
assert_eq!(epic, 8445);
assert_eq!(item, "the gap");
assert_eq!(why, "later");
assert_eq!(destination, "unscheduled");
}
other => panic!("unexpected: {other:?}"),
}
assert!(
Cli::try_parse_from([
"trusty-mpm",
"issue",
"epic",
"defer",
"8445",
"--item",
"x"
])
.is_err()
);
}
#[test]
fn cli_parses_issue_epic_close() {
use crate::cli::{EpicCmd, IssueCmd};
let cli = Cli::try_parse_from([
"trusty-mpm",
"issue",
"epic",
"close",
"8445",
"--evidence",
"O1: PR #1",
"--evidence",
"O2: PR #2",
])
.unwrap();
match cli.command.unwrap() {
Command::Issue {
cmd: IssueCmd::Epic(EpicCmd::Close { epic, evidence }),
..
} => {
assert_eq!(epic, 8445);
assert_eq!(evidence, vec!["O1: PR #1", "O2: PR #2"]);
}
other => panic!("unexpected: {other:?}"),
}
let cli = Cli::try_parse_from(["trusty-mpm", "issue", "epic", "close", "8445"]).unwrap();
assert!(matches!(
cli.command.unwrap(),
Command::Issue {
cmd: IssueCmd::Epic(EpicCmd::Close { epic: 8445, .. }),
..
}
));
}
#[test]
fn cli_parses_watch_poll_minimal() {
use crate::cli::WatchCmd;
let cli = Cli::try_parse_from(["trusty-mpm", "watch", "poll", "owner/repo"]).unwrap();
match cli.command.unwrap() {
Command::Watch {
cmd: WatchCmd::Poll { args },
} => {
assert_eq!(args.project, "owner/repo");
assert!(args.label.is_none());
assert!(!args.dry_run);
assert!(!args.execute, "execute must default off (safe)");
assert!(args.interval_secs.is_none());
assert!(args.state.is_none());
assert_eq!(args.runtime, trusty_mpm::runtime::RuntimeKind::ClaudeCode);
}
other => panic!("expected watch poll, got {other:?}"),
}
}
#[test]
fn cli_parses_watch_poll_with_flags() {
use crate::cli::WatchCmd;
use crate::commands::watch::github::IssueState;
let cli = Cli::try_parse_from([
"trusty-mpm",
"watch",
"poll",
"acme/widget",
"--label",
"ship-it",
"--state",
"all",
"--execute",
])
.unwrap();
match cli.command.unwrap() {
Command::Watch {
cmd: WatchCmd::Poll { args },
} => {
assert_eq!(args.project, "acme/widget");
assert_eq!(args.label.as_deref(), Some("ship-it"));
assert_eq!(args.state, Some(IssueState::All));
assert!(args.execute);
}
other => panic!("expected watch poll, got {other:?}"),
}
}
#[test]
fn cli_parses_watch_listen_with_interval() {
use crate::cli::WatchCmd;
let cli = Cli::try_parse_from([
"trusty-mpm",
"watch",
"listen",
"owner/repo",
"--interval-secs",
"30",
"--dry-run",
])
.unwrap();
match cli.command.unwrap() {
Command::Watch {
cmd: WatchCmd::Listen { args },
} => {
assert_eq!(args.project, "owner/repo");
assert_eq!(args.interval_secs, Some(30));
assert!(args.dry_run);
}
other => panic!("expected watch listen, got {other:?}"),
}
}
#[test]
fn cli_parses_login_standalone() {
let cli = Cli::try_parse_from(["trusty-mpm", "login"]).unwrap();
assert!(matches!(cli.command.unwrap(), Command::Login { .. }));
}
#[test]
fn image_clip_has_correct_dimensions() {
use crate::formatters::banner::{shade_image, source::DEFAULT_BANNER_ART};
let (lines, cols) = shade_image(DEFAULT_BANNER_ART, false);
let raw_count = DEFAULT_BANNER_ART.lines().count();
assert_eq!(
lines.len(),
raw_count,
"shade_image row count must match art line count"
);
assert!(cols > 0, "auto-sized cols must be > 0");
assert!(cols >= 20, "compact-splash art is at least 20 columns wide");
}
#[test]
fn art_char_shading_emits_truecolor() {
use crate::formatters::banner::{shade_image, source::DEFAULT_BANNER_ART};
let (lines, _) = shade_image(DEFAULT_BANNER_ART, true);
let any_colored = lines.iter().any(|l| l.contains("\x1B[38;2;"));
assert!(
any_colored,
"shaded art must emit truecolor escapes when color is enabled"
);
}
#[test]
fn art_shading_no_ansi_when_color_disabled() {
use crate::formatters::banner::{shade_image, source::DEFAULT_BANNER_ART};
let (lines, _) = shade_image(DEFAULT_BANNER_ART, false);
for line in &lines {
assert!(
!line.contains('\x1B'),
"shade_image must not emit ANSI escapes when color is disabled: {line:?}"
);
}
}
#[test]
fn art_shading_spaces_are_uncolored() {
use crate::formatters::banner::{
shade_image, source::DEFAULT_BANNER_ART, two_panel::strip_ansi,
};
let (lines, _) = shade_image(DEFAULT_BANNER_ART, true);
let has_space = lines.iter().any(|l| strip_ansi(l).contains(' '));
assert!(has_space, "shaded art must contain some uncolored spaces");
}
#[test]
fn narrow_fallback_reconnect_box_includes_reconnecting_text() {
use crate::formatters::banner::two_panel::{render_two_panel_banner, strip_ansi};
use crate::formatters::info_box::{ConsoleInfo, DaemonInfo, WelcomeData};
colored::control::set_override(false);
let data = WelcomeData {
project: "my-project".to_string(),
workspace: "/tmp/my-project".to_string(),
user: String::new(),
reconnecting: true,
session_name: "tmpm-my-project".to_string(),
daemon: DaemonInfo::default(),
console: ConsoleInfo::default(),
recent_commits: vec![],
memory_status: "(not detected)".to_string(),
search_status: "(not detected)".to_string(),
review_status: "(not detected)".to_string(),
};
let out = render_two_panel_banner(&data, 80, true).expect("narrow box");
let bare = strip_ansi(&out);
assert!(
bare.contains("Reconnecting..."),
"narrow reconnect box must contain 'Reconnecting...' label"
);
let first = bare.lines().next().unwrap_or("");
assert!(first.starts_with('β'), "must have outer box top border");
colored::control::unset_override();
}
#[test]
fn narrow_fallback_normal_box_does_not_include_reconnecting_text() {
use crate::formatters::banner::two_panel::{render_two_panel_banner, strip_ansi};
use crate::formatters::info_box::{ConsoleInfo, DaemonInfo, WelcomeData};
colored::control::set_override(false);
let data = WelcomeData {
project: "my-project".to_string(),
workspace: "/tmp/my-project".to_string(),
user: String::new(),
reconnecting: false,
session_name: String::new(),
daemon: DaemonInfo::default(),
console: ConsoleInfo::default(),
recent_commits: vec![],
memory_status: "(not detected)".to_string(),
search_status: "(not detected)".to_string(),
review_status: "(not detected)".to_string(),
};
let out = render_two_panel_banner(&data, 80, false).expect("narrow box");
let bare = strip_ansi(&out);
assert!(
!bare.contains("Reconnecting..."),
"normal launch narrow box must not contain 'Reconnecting...' label"
);
colored::control::unset_override();
}
#[test]
fn cli_parses_banner() {
use crate::cli::{Cli, Command};
use clap::Parser;
let cli = Cli::try_parse_from(["tm", "banner"]).expect("banner must parse");
assert!(
matches!(
cli.command,
Some(Command::Banner {
reconnecting: false
})
),
"expected Banner {{ reconnecting: false }}"
);
}
#[test]
fn cli_parses_banner_reconnecting() {
use crate::cli::{Cli, Command};
use clap::Parser;
let cli = Cli::try_parse_from(["tm", "banner", "--reconnecting"])
.expect("banner --reconnecting must parse");
assert!(
matches!(cli.command, Some(Command::Banner { reconnecting: true })),
"expected Banner {{ reconnecting: true }}"
);
}
#[test]
fn banner_preview_no_clear_escape() {
use crate::formatters::banner::two_panel::{render_two_panel_banner, strip_ansi};
use crate::formatters::info_box::{ConsoleInfo, DaemonInfo, WelcomeData};
colored::control::set_override(false);
let data = WelcomeData {
project: "p".to_string(),
workspace: "/tmp/p".to_string(),
user: String::new(),
reconnecting: false,
session_name: String::new(),
daemon: DaemonInfo::default(),
console: ConsoleInfo::default(),
recent_commits: vec![],
memory_status: "(not detected)".to_string(),
search_status: "(not detected)".to_string(),
review_status: "(not detected)".to_string(),
};
let out = render_two_panel_banner(&data, 120, false).expect("banner");
assert!(
!out.starts_with("\x1B[2J"),
"compositor output must not start with screen-clear escape"
);
let bare = strip_ansi(&out);
assert!(
bare.contains(env!("CARGO_PKG_VERSION")),
"compositor output must contain version in title bar"
);
colored::control::unset_override();
}
#[test]
fn banner_preview_does_not_panic() {
crate::formatters::banner::print_banner_preview(false);
crate::formatters::banner::print_banner_preview(true);
}
#[test]
fn health_catalog_stale_message_contains_action() {
assert!(
CATALOG_STALE_MSG.contains("tm catalog sync"),
"CATALOG_STALE_MSG must name the fix command: {CATALOG_STALE_MSG:?}"
);
assert!(
CATALOG_UNKNOWN_MSG.contains("tm catalog sync"),
"CATALOG_UNKNOWN_MSG must name the fix command: {CATALOG_UNKNOWN_MSG:?}"
);
}
#[test]
fn non_git_dir_fallback_prints_help_hint() {
assert!(
NON_GIT_FALLBACK_HINT.contains("--help"),
"NON_GIT_FALLBACK_HINT must reference --help for discoverability: \
{NON_GIT_FALLBACK_HINT:?}"
);
}
#[test]
fn cli_parses_memory_recall() {
let cli = Cli::try_parse_from(["trusty-mpm", "memory", "recall", "who owns the ledger"])
.expect("`tm memory recall <query>` must parse");
match cli.command.unwrap() {
Command::Memory {
action:
MemoryAction::Recall {
query,
palace,
top_k,
room,
wing,
min_score,
json,
memory_socket,
},
} => {
assert_eq!(query, "who owns the ledger");
assert!(palace.is_none(), "the palace defaults to the session's own");
assert!(top_k.is_none(), "top_k defaults to trusty-memory's own");
assert!(room.is_none());
assert!(wing.is_none());
assert!(min_score.is_none());
assert!(!json);
assert!(memory_socket.is_none());
}
other => panic!("expected Memory/Recall, got {other:?}"),
}
}
#[test]
fn cli_parses_memory_recall_with_overrides() {
let cli = Cli::try_parse_from([
"trusty-mpm",
"memory",
"recall",
"q",
"--palace",
"other-palace",
"--top-k",
"3",
"--room",
"Planning",
"--min-score",
"0.4",
"--json",
"--memory-socket",
"/tmp/trusty-memory.sock",
])
.expect("every recall flag must parse");
match cli.command.unwrap() {
Command::Memory {
action:
MemoryAction::Recall {
palace,
top_k,
room,
min_score,
json,
memory_socket,
..
},
} => {
assert_eq!(palace.as_deref(), Some("other-palace"));
assert_eq!(top_k, Some(3));
assert_eq!(room.as_deref(), Some("Planning"));
assert_eq!(min_score, Some(0.4));
assert!(json);
assert_eq!(
memory_socket.as_deref(),
Some(std::path::Path::new("/tmp/trusty-memory.sock"))
);
}
other => panic!("expected Memory/Recall, got {other:?}"),
}
}
#[test]
fn cli_refuses_memory_recall_wing_with_room() {
let err = Cli::try_parse_from([
"trusty-mpm",
"memory",
"recall",
"q",
"--room",
"Planning",
"--wing",
"engineer",
])
.expect_err("wing and room together must be refused at parse time");
assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict);
}
#[test]
fn cli_parses_memory_remember_with_tags() {
let cli = Cli::try_parse_from([
"trusty-mpm",
"memory",
"remember",
"the ledger is owned by ops",
"--tag",
"ownership",
"--tag",
"ledger",
"--room",
"Planning",
"--fact-key=ws:s1/resume",
"--expires-at=2026-10-06T12:00:00Z",
])
.expect("`tm memory remember <text>` must parse");
match cli.command.unwrap() {
Command::Memory {
action:
MemoryAction::Remember {
text,
palace,
room,
tags,
fact_key,
expires_at,
json,
memory_socket,
},
} => {
assert_eq!(text, "the ledger is owned by ops");
assert_eq!(fact_key.as_deref(), Some("ws:s1/resume")); assert_eq!(expires_at.as_deref(), Some("2026-10-06T12:00:00Z"));
assert!(palace.is_none());
assert_eq!(room.as_deref(), Some("Planning"));
assert_eq!(tags, vec!["ownership".to_string(), "ledger".to_string()]);
assert!(!json);
assert!(memory_socket.is_none());
}
other => panic!("expected Memory/Remember, got {other:?}"),
}
}
#[test]
fn cli_parses_memory_note() {
let cli = Cli::try_parse_from([
"trusty-mpm",
"memory",
"note",
"deploy target is prod-east",
"--palace",
"p",
"--json",
])
.expect("`tm memory note <content>` must parse");
match cli.command.unwrap() {
Command::Memory {
action:
MemoryAction::Note {
content,
palace,
tags,
json,
..
},
} => {
assert_eq!(content, "deploy target is prod-east");
assert_eq!(palace.as_deref(), Some("p"));
assert!(tags.is_empty());
assert!(json);
}
other => panic!("expected Memory/Note, got {other:?}"),
}
}
#[test]
fn cli_parses_memory_forget() {
let cli = Cli::try_parse_from([
"trusty-mpm",
"memory",
"forget",
"0b6f3c1e-8a52-4c1d-9e0f-2a7b5c4d3e21",
"--palace",
"p",
"--memory-socket",
"/tmp/m.sock",
"--json",
])
.expect("`tm memory forget <drawer-id>` must parse");
match cli.command.unwrap() {
Command::Memory {
action:
MemoryAction::Forget {
drawer_id,
fact_key,
palace,
json,
memory_socket,
},
} => {
assert_eq!(
drawer_id.as_deref(),
Some("0b6f3c1e-8a52-4c1d-9e0f-2a7b5c4d3e21")
);
assert_eq!(fact_key, None);
assert_eq!(palace.as_deref(), Some("p"));
assert_eq!(memory_socket, Some(std::path::PathBuf::from("/tmp/m.sock")));
assert!(json);
}
other => panic!("expected Memory/Forget, got {other:?}"),
}
assert!(Cli::try_parse_from(["trusty-mpm", "memory", "forget"]).is_err());
}
#[test]
fn cli_parses_memory_forget_by_fact_key() {
let forget = |extra: &[&str]| {
let mut argv = vec!["trusty-mpm", "memory", "forget"];
argv.extend_from_slice(extra);
Cli::try_parse_from(argv)
};
assert!(
forget(&["--fact-key", "pr:9340/state"]).is_ok(),
"`forget --fact-key <key>` must parse"
);
assert!(
forget(&[
"0b6f3c1e-8a52-4c1d-9e0f-2a7b5c4d3e21",
"--fact-key",
"pr:9340/state"
])
.is_err(),
"an id and --fact-key together must be refused"
);
assert!(
forget(&[]).is_err(),
"neither an id nor --fact-key: refused"
);
}
#[test]
fn cli_parses_shell_init() {
for (arg, want) in [
("zsh", ShellArg::Zsh),
("bash", ShellArg::Bash),
("fish", ShellArg::Fish),
] {
let cli = Cli::try_parse_from(["trusty-mpm", "shell-init", arg]).unwrap();
match cli.command.unwrap() {
Command::ShellInit { shell } => assert_eq!(shell, want),
other => panic!("expected ShellInit, got {other:?}"),
}
}
assert!(Cli::try_parse_from(["trusty-mpm", "shell-init", "ksh"]).is_err());
assert!(Cli::try_parse_from(["trusty-mpm", "shell-init"]).is_err());
}
#[test]
fn cli_parses_doctor_quarantine_mcp() {
let cli = Cli::try_parse_from(["trusty-mpm", "doctor", "--quarantine-mcp", "/tmp/.mcp.json"])
.unwrap();
let Command::Doctor { flags } = cli.command.unwrap() else {
panic!("expected doctor");
};
assert_eq!(
flags.quarantine_mcp.as_deref(),
Some(std::path::Path::new("/tmp/.mcp.json"))
);
assert!(!flags.yes, "it must default to a preview");
assert!(!flags.fix, "it must not imply the full --fix sweep");
let cli = Cli::try_parse_from([
"trusty-mpm",
"doctor",
"--quarantine-mcp",
"/tmp/.mcp.json",
"--yes",
])
.unwrap();
let Command::Doctor { flags } = cli.command.unwrap() else {
panic!("expected doctor");
};
assert!(flags.yes, "--yes must promote a quarantine, not just --fix");
}
#[test]
fn cli_rejects_doctor_yes_without_a_write_action() {
assert!(Cli::try_parse_from(["trusty-mpm", "doctor", "--yes"]).is_err());
assert!(Cli::try_parse_from(["trusty-mpm", "doctor", "--quarantine-mcp"]).is_err());
}
#[test]
fn cli_parses_pr_open() {
use crate::cli::PrCmd;
let cli = Cli::try_parse_from([
"trusty-mpm",
"pr",
"open",
"--title",
"feat(x): a thing",
"--body-file",
"/tmp/body.md",
"--issue",
"6653",
"--rung",
"3",
"--docs-only",
"--dry-run",
])
.unwrap();
let Some(Command::Pr {
cmd: PrCmd::Open(args),
}) = cli.command
else {
panic!("expected pr open");
};
assert_eq!(args.title, "feat(x): a thing");
assert_eq!(args.body_file.to_string_lossy(), "/tmp/body.md");
assert_eq!(args.issue, Some(6653));
assert_eq!(args.rung, Some(3));
assert_eq!(args.base, "main", "base defaults to main");
assert!(args.docs_only);
assert!(args.dry_run);
assert!(!args.closes, "Closes is opt-in, never the default");
assert!(
!args.minimal,
"the nine-field contract is the default, never opt-in (#7615)"
);
}
#[test]
fn cli_parses_pr_open_minimal() {
use crate::cli::PrCmd;
let cli = Cli::try_parse_from([
"trusty-mpm",
"pr",
"open",
"--title",
"fix(x): a thing",
"--body-file",
"/tmp/body.md",
"--minimal",
])
.unwrap();
let Some(Command::Pr {
cmd: PrCmd::Open(args),
}) = cli.command
else {
panic!("expected pr open");
};
assert!(args.minimal);
assert!(!args.docs_only, "--minimal is not --docs-only");
}
#[test]
fn cli_parses_pr_merge() {
use crate::cli::PrCmd;
let cli = Cli::try_parse_from([
"trusty-mpm",
"pr",
"merge",
"6808",
"--auto",
"--no-delete-branch",
"--repo",
"bobmatnyc/trusty-tools",
])
.unwrap();
let Some(Command::Pr {
cmd: PrCmd::Merge(args),
}) = cli.command
else {
panic!("expected pr merge");
};
assert_eq!(args.pr, 6808);
assert!(args.auto);
assert!(args.no_delete_branch);
assert_eq!(args.repo.as_deref(), Some("bobmatnyc/trusty-tools"));
let bare = Cli::try_parse_from(["trusty-mpm", "pr", "merge", "6808"]).unwrap();
let Some(Command::Pr {
cmd: PrCmd::Merge(args),
}) = bare.command
else {
panic!("expected pr merge");
};
assert!(!args.auto, "auto-merge is opt-in");
assert!(
!args.no_delete_branch,
"the remote branch is deleted by default"
);
}
#[test]
fn cli_parses_pr_cleanup() {
use crate::cli::PrCmd;
let cli = Cli::try_parse_from([
"trusty-mpm",
"pr",
"cleanup",
"7275",
"--repo",
"bobmatnyc/trusty-tools",
"--dry-run",
])
.unwrap();
let Some(Command::Pr {
cmd: PrCmd::Cleanup(args),
}) = cli.command
else {
panic!("expected pr cleanup");
};
assert_eq!(args.pr, 7275);
assert_eq!(args.repo.as_deref(), Some("bobmatnyc/trusty-tools"));
assert!(args.dry_run);
let bare = Cli::try_parse_from(["trusty-mpm", "pr", "cleanup", "7275"]).unwrap();
let Some(Command::Pr {
cmd: PrCmd::Cleanup(args),
}) = bare.command
else {
panic!("expected pr cleanup");
};
assert!(
!args.dry_run,
"cleanup performs the work by default; --dry-run is the preview"
);
assert_eq!(args.repo, None);
}
#[test]
fn cli_pr_cleanup_help_states_the_steps() {
use clap::CommandFactory as _;
let mut root = Cli::command();
let help = root
.find_subcommand_mut("pr")
.expect("pr group")
.find_subcommand_mut("cleanup")
.expect("cleanup verb")
.render_long_help()
.to_string();
for needle in [
"MERGED",
"remote-branch",
"worktree",
"local-branch",
"prune",
"never passes `--force`",
"record-only",
"--dry-run",
] {
assert!(help.contains(needle), "`pr cleanup --help` omits {needle}");
}
}
#[test]
fn cli_pr_merge_help_states_the_refusals() {
use clap::CommandFactory as _;
let mut root = Cli::command();
let help = root
.find_subcommand_mut("pr")
.expect("pr group")
.find_subcommand_mut("merge")
.expect("merge verb")
.render_long_help()
.to_string();
for needle in [
"draft",
"do-not-merge",
"CHANGES_REQUESTED",
"CONFLICTING",
"DIRTY",
"gh pr update-branch",
"BEHIND is NOT a refusal",
"left to `gh pr merge` to accept or reject",
"auto-merge fires",
"merge queue",
] {
assert!(
help.contains(needle),
"`--help` must state {needle}:\n{help}"
);
}
}
#[test]
fn cli_rejects_pr_open_rung_out_of_range() {
let args = [
"trusty-mpm",
"pr",
"open",
"--title",
"t",
"--body-file",
"/tmp/b.md",
"--rung",
"7",
];
assert!(Cli::try_parse_from(args).is_err(), "rung 7 must not parse");
}
#[test]
fn cli_parses_pr_queue_check() {
use crate::cli::PrCmd;
let cli = Cli::try_parse_from(["trusty-mpm", "pr", "queue-check"]).unwrap();
let Some(Command::Pr {
cmd: PrCmd::QueueCheck(args),
}) = cli.command
else {
panic!("expected pr queue-check");
};
assert_eq!(args.base, "main");
assert_eq!(args.pr, None);
assert!(!args.json);
let cli = Cli::try_parse_from(["trusty-mpm", "pr", "queue-check", "42", "--json"]).unwrap();
let Some(Command::Pr {
cmd: PrCmd::QueueCheck(args),
}) = cli.command
else {
panic!("expected pr queue-check");
};
assert_eq!(args.pr, Some(42));
assert!(args.json);
}
#[test]
fn statusline_help_names_every_savings_stdin_field() {
use crate::commands::statusline::SAVINGS_SEGMENT_STDIN_FIELDS;
use clap::CommandFactory as _;
let mut root = Cli::command();
let help = root
.find_subcommand_mut("statusline")
.expect("statusline subcommand exists")
.render_long_help()
.to_string();
let marker = "Stdin fields read: ";
let start = help
.find(marker)
.expect("--help names the stdin fields the πΈ segment reads")
+ marker.len();
let rest = &help[start..];
let end = rest
.find(". ")
.expect("the 'Stdin fields read:' sentence ends with '. '");
let sentence = &rest[..end];
let mut help_fields: Vec<&str> = sentence
.split(", ")
.map(|field| field.trim().trim_matches('`'))
.collect();
help_fields.sort_unstable();
let mut expected: Vec<&str> = SAVINGS_SEGMENT_STDIN_FIELDS
.iter()
.map(|(field, _reason)| *field)
.collect();
expected.sort_unstable();
assert_eq!(
help_fields, expected,
"`tm statusline --help`'s stdin field list must match \
SAVINGS_SEGMENT_STDIN_FIELDS exactly -- a field the πΈ segment reads \
went missing from --help, or --help names one the code does not read"
);
}