trusty-mpm 1.8.2

trusty-mpm: unified multi-agent orchestration platform (core, daemon, CLI, TUI, Telegram)
//! CLI parse tests for `tm repair` (issues #2603, #2867).
//!
//! Why: `tm repair push-guard` is the only supported way to retrofit the
//! #2867 cross-branch push guard onto an already-provisioned clone, so its
//! invocation surface — the verb name doctor prints, and both flags — is a
//! contract. A rename that silently breaks doctor's printed remediation is
//! exactly the failure this pins.
//! What: `cli_parses_repair_deploy`, `cli_parses_repair_push_guard`,
//! `cli_parses_repair_push_guard_flags`.
//! Test: this module IS the test suite for the `tm repair` CLI surface.

use clap::Parser;

use crate::cli::{Cli, Command, RepairAction};

#[test]
fn cli_parses_repair_deploy() {
    let cli = Cli::try_parse_from(["trusty-mpm", "repair", "deploy"]).unwrap();
    match cli.command.unwrap() {
        Command::Repair {
            action: RepairAction::Deploy { force },
        } => assert!(!force),
        other => panic!("expected Command::Repair(Deploy), got {other:?}"),
    }
}

#[test]
fn cli_parses_repair_push_guard() {
    let cli = Cli::try_parse_from(["trusty-mpm", "repair", "push-guard"]).unwrap();
    match cli.command.unwrap() {
        Command::Repair {
            action: RepairAction::PushGuard { path, dry_run },
        } => {
            assert_eq!(path, None, "the default target is the current directory");
            assert!(!dry_run, "a bare invocation must actually install");
        }
        other => panic!("expected Command::Repair(PushGuard), got {other:?}"),
    }
}

#[test]
fn cli_parses_repair_push_guard_flags() {
    let cli = Cli::try_parse_from([
        "trusty-mpm",
        "repair",
        "push-guard",
        "--path",
        "/tmp/some-clone",
        "--dry-run",
    ])
    .unwrap();
    match cli.command.unwrap() {
        Command::Repair {
            action: RepairAction::PushGuard { path, dry_run },
        } => {
            assert_eq!(path, Some("/tmp/some-clone".to_string()));
            assert!(dry_run);
        }
        other => panic!("expected Command::Repair(PushGuard), got {other:?}"),
    }
}

/// Why (#5007): `tm doctor`'s `session_store` verdict and the store's own error
/// message both name this verb as the fix. A rename would break both printed
/// remediations at once, exactly as the push-guard tests above guard against.
/// What: pins the bare invocation and its defaults.
/// Test: this test.
#[test]
fn cli_parses_repair_session_store() {
    let cli = Cli::try_parse_from(["trusty-mpm", "repair", "session-store"]).unwrap();
    match cli.command.unwrap() {
        Command::Repair {
            action:
                RepairAction::SessionStore {
                    path,
                    dry_run,
                    force,
                },
        } => {
            assert_eq!(path, None, "the default target is the real store");
            assert!(!dry_run, "a bare invocation actually repairs");
            assert!(!force, "a bare invocation refuses an orphaned tail");
        }
        other => panic!("expected Command::Repair(SessionStore), got {other:?}"),
    }
}

/// Why (#7602): `--force` is the operator asserting an owner the daemon cannot
/// confirm is gone, so it must be opt-in — a default-on flag would let the verb
/// end a record for a session that is merely unregistered.
/// What: pins the verb name, its positional agent id, and the refusing default.
/// Test: this test.
#[test]
fn cli_parses_repair_delegation() {
    let cli =
        Cli::try_parse_from(["trusty-mpm", "repair", "delegation", "af20cc838b2b30a55"]).unwrap();
    match cli.command.unwrap() {
        Command::Repair {
            action: RepairAction::Delegation {
                agent_id, force, ..
            },
        } => {
            assert_eq!(agent_id.as_deref(), Some("af20cc838b2b30a55"));
            assert!(
                !force,
                "a bare invocation must not assert the owner is gone"
            );
        }
        other => panic!("expected Command::Repair(Delegation), got {other:?}"),
    }
}

/// Why (#7602): the undeterminable-owner arm is the one an operator actually
/// hits after a daemon restart, so its escape has to be reachable.
/// What: pins `--force`.
/// Test: this test.
#[test]
fn cli_parses_repair_delegation_by_id_and_list_8257() {
    let by_id = Cli::try_parse_from([
        "trusty-mpm",
        "repair",
        "delegation",
        "--delegation-id",
        "64237d1c-0aa4-4090-bd14-d3c273da7e95",
    ])
    .unwrap();
    match by_id.command.unwrap() {
        Command::Repair {
            action:
                RepairAction::Delegation {
                    agent_id,
                    delegation_id,
                    list,
                    ..
                },
        } => {
            assert_eq!(agent_id, None);
            assert_eq!(
                delegation_id.as_deref(),
                Some("64237d1c-0aa4-4090-bd14-d3c273da7e95")
            );
            assert_eq!(list, None);
        }
        other => panic!("expected Command::Repair(Delegation), got {other:?}"),
    }
    let list = Cli::try_parse_from(["trusty-mpm", "repair", "delegation", "--list"]).unwrap();
    match list.command.unwrap() {
        Command::Repair {
            action: RepairAction::Delegation { list, .. },
        } => assert_eq!(list, Some(std::path::PathBuf::from("."))),
        other => panic!("expected Command::Repair(Delegation), got {other:?}"),
    }
    // A bare invocation names nothing, and a listing never writes.
    assert!(Cli::try_parse_from(["trusty-mpm", "repair", "delegation"]).is_err());
    assert!(
        Cli::try_parse_from(["trusty-mpm", "repair", "delegation", "--list", "--force"]).is_err()
    );
}

/// Why (#8257 owner ruling): the owning session may clear its own record, and
/// the daemon learns the caller only from the harness's
/// `CLAUDE_CODE_SESSION_ID`. An argument naming a session would let any caller
/// claim to be the owner.
/// What: every spelling a caller might use to name a session is rejected.
/// Test: this test; the daemon side is
/// `repair_route_ignores_an_owner_id_the_caller_supplies_8257`.
#[test]
fn cli_rejects_a_caller_session_argument_8257() {
    let owner = "64237d1c-0aa4-4090-bd14-d3c273da7e95";
    for extra in [
        vec!["--session", owner],
        vec!["--session-id", owner],
        vec!["--caller-session", owner],
        vec!["--owner", owner],
        vec![owner],
    ] {
        let mut argv = vec!["trusty-mpm", "repair", "delegation", "a0wner"];
        argv.extend(extra.iter().copied());
        assert!(
            Cli::try_parse_from(&argv).is_err(),
            "`{}` must not parse: no argument names the caller session",
            argv.join(" ")
        );
    }
}

#[test]
fn cli_parses_repair_delegation_force() {
    let cli = Cli::try_parse_from([
        "trusty-mpm",
        "repair",
        "delegation",
        "af20cc838b2b30a55",
        "--force",
    ])
    .unwrap();
    match cli.command.unwrap() {
        Command::Repair {
            action: RepairAction::Delegation { force, .. },
        } => assert!(force),
        other => panic!("expected Command::Repair(Delegation), got {other:?}"),
    }
}

/// Why (#7569): the command REWRITES the operator's savings ledger, so the
/// safe default is the contract — a bare invocation must report and write
/// nothing. A flag that defaulted the other way would destroy rows before
/// anyone authorised it.
/// What: pins the verb name and the write-nothing default.
/// Test: this test.
#[test]
fn cli_parses_repair_savings_ledger() {
    let cli = Cli::try_parse_from(["trusty-mpm", "repair", "savings-ledger"]).unwrap();
    match cli.command.unwrap() {
        Command::Repair {
            action:
                RepairAction::SavingsLedger {
                    root,
                    apply,
                    dry_run,
                    markers,
                },
        } => {
            assert_eq!(root, None, "the default target is the real framework root");
            assert!(!apply, "a bare invocation writes nothing");
            assert!(!dry_run, "dry-run is the default, not a flag it must carry");
            assert!(!markers, "a bare invocation leaves the markers alone");
        }
        other => panic!("expected Command::Repair(SavingsLedger), got {other:?}"),
    }
}

/// Why (#7569): `--apply` is the only way past the dry run, `--root` the only
/// way to rehearse against a copy, and `--markers` the only way to sweep the
/// stray marker directory; all three must be reachable.
/// What: pins the three flags together.
/// Test: this test.
#[test]
fn cli_parses_repair_savings_ledger_flags() {
    let cli = Cli::try_parse_from([
        "trusty-mpm",
        "repair",
        "savings-ledger",
        "--root",
        "/tmp/rehearsal",
        "--apply",
        "--markers",
    ])
    .unwrap();
    match cli.command.unwrap() {
        Command::Repair {
            action:
                RepairAction::SavingsLedger {
                    root,
                    apply,
                    dry_run,
                    markers,
                },
        } => {
            assert_eq!(root, Some("/tmp/rehearsal".to_string()));
            assert!(apply);
            assert!(!dry_run);
            assert!(markers);
        }
        other => panic!("expected Command::Repair(SavingsLedger), got {other:?}"),
    }
}

/// Why (#7569): `--apply` and `--dry-run` state opposite intentions, and a
/// command that silently picked one would repair a ledger an operator meant
/// only to inspect.
/// Test: this test.
#[test]
fn cli_rejects_repair_savings_ledger_apply_with_dry_run() {
    assert!(
        Cli::try_parse_from([
            "trusty-mpm",
            "repair",
            "savings-ledger",
            "--apply",
            "--dry-run",
        ])
        .is_err(),
        "--apply and --dry-run must not be combinable"
    );
}

/// Why: `--dry-run` is the only way to inspect the cut before authorising it
/// and `--force` the only way past the orphan refusal; both must be reachable.
/// What: pins all three flags.
/// Test: this test.
#[test]
fn cli_parses_repair_session_store_flags() {
    let cli = Cli::try_parse_from([
        "trusty-mpm",
        "repair",
        "session-store",
        "--path",
        "/tmp/sessions.json",
        "--dry-run",
        "--force",
    ])
    .unwrap();
    match cli.command.unwrap() {
        Command::Repair {
            action:
                RepairAction::SessionStore {
                    path,
                    dry_run,
                    force,
                },
        } => {
            assert_eq!(path, Some("/tmp/sessions.json".to_string()));
            assert!(dry_run);
            assert!(force);
        }
        other => panic!("expected Command::Repair(SessionStore), got {other:?}"),
    }
}