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::{deploy_summary_line, short_id};
#[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_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!("scalar")), "--------");
}
#[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 { argv }) = cli.command else {
panic!("expected internal-spawn-disclaimed");
};
assert_eq!(argv, ["claude", "-p"]);
}
#[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,
},
}) = 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_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: false,
yes: false,
include_frozen: false,
}
}
));
}
#[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: false,
yes: false,
include_frozen: 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: false,
yes: false,
include_frozen: 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"
);
}
#[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 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::{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(),
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::{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(),
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_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 } => {
assert_eq!(url.as_deref(), Some(DEFAULT_URL));
assert_eq!(interval_ms, 1000);
}
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 err = Cli::try_parse_from(["trusty-mpm", "coordinator-tui"]).unwrap_err();
assert_eq!(
err.kind(),
clap::error::ErrorKind::InvalidSubcommand,
"expected InvalidSubcommand, got {:?}",
err.kind()
);
}
#[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,
} => {
assert_eq!(addr.to_string(), "127.0.0.1:7880");
assert!(!tailscale);
assert!(!mcp);
assert!(!force);
}
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 }
));
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_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 } => {
assert!(dry_run);
assert!(config.is_none());
}
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 gui_not_found_error_has_install_hint() {
let not_found = std::io::Error::new(std::io::ErrorKind::NotFound, "no such file");
let err = crate::gui_status_to_result(Err(not_found))
.expect_err("NotFound spawn error must map to an Err");
let msg = err.to_string();
assert!(
msg.contains("cargo install trusty-mpm-gui"),
"expected install hint, got: {msg}"
);
assert!(
msg.contains("not installed"),
"expected 'not installed' phrasing, got: {msg}"
);
}
#[test]
fn gui_binary_resolution_falls_back_to_bare_name() {
let resolved = crate::resolve_gui_binary();
assert_eq!(
resolved.file_name().and_then(|n| n.to_str()),
Some("trusty-mpm-gui"),
"resolver must yield a trusty-mpm-gui path, got: {resolved:?}"
);
}
#[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_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::{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(),
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::{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(),
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::{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(),
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_import() {
let cli = Cli::try_parse_from([
"trusty-mpm",
"memory",
"import",
"/tmp/mem",
"--palace",
"trusty-tools",
])
.unwrap();
match cli.command.unwrap() {
Command::Memory {
action:
MemoryAction::Import {
dir,
palace,
dry_run,
json,
allow_secret_like,
memory_url,
},
} => {
assert_eq!(dir, std::path::PathBuf::from("/tmp/mem"));
assert_eq!(palace, "trusty-tools");
assert!(!dry_run, "writes are the default mode");
assert!(!json);
assert!(!allow_secret_like);
assert!(memory_url.is_none(), "URL defaults to daemon discovery");
}
other => panic!("expected Memory/Import, got {other:?}"),
}
}
#[test]
fn cli_parses_memory_import_dry_run_json() {
let cli = Cli::try_parse_from([
"trusty-mpm",
"memory",
"import",
"/tmp/mem",
"--palace",
"p",
"--dry-run",
"--json",
"--allow-secret-like",
"--memory-url",
"http://127.0.0.1:7070",
])
.unwrap();
match cli.command.unwrap() {
Command::Memory {
action:
MemoryAction::Import {
dry_run,
json,
allow_secret_like,
memory_url,
..
},
} => {
assert!(dry_run);
assert!(json);
assert!(allow_secret_like);
assert_eq!(memory_url.as_deref(), Some("http://127.0.0.1:7070"));
}
other => panic!("expected Memory/Import, got {other:?}"),
}
}
#[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());
}