govctl 0.9.3

Project governance CLI for RFC, ADR, and Work Item management
use super::*;

#[test]
fn test_edit_plans_are_mutating() -> Result<(), Box<dyn std::error::Error>> {
    let plan = crate::RfcCommand::Edit(crate::CommonEditArgs {
        id: "RFC-0001".to_string(),
        path: "title".to_string(),
        action: EditActionArgs {
            set: Some(Some("X".to_string())),
            add: None,
            remove: None,
            tick: None,
            stdin: false,
            at: None,
            exact: false,
            regex: false,
            all: false,
        },
    })
    .to_plan()?;
    assert!(matches!(plan.scope, Scope::Target { .. }));
    assert!(matches!(plan.op, Op::Edit(EditOp::Field { .. })));
    assert_eq!(plan.lock_disposition(), LockDisposition::GovRootExclusive);

    let plan = ClauseCommand::Edit {
        id: "RFC-0001:C-TEST".to_string(),
        path: None,
        set: None,
        add: None,
        remove: None,
        tick: None,
        stdin: true,
        at: None,
        exact: false,
        regex: false,
        all: false,
        text: None,
        text_file: None,
    }
    .to_plan()?;
    assert!(matches!(plan.op, Op::Edit(EditOp::ClauseLegacy { .. })));
    assert_eq!(plan.lock_disposition(), LockDisposition::GovRootExclusive);
    Ok(())
}

#[test]
fn test_read_plans_are_lock_free() -> Result<(), Box<dyn std::error::Error>> {
    let status = global(Op::Builtin(BuiltinOp::Status));
    assert_eq!(status.lock_disposition(), LockDisposition::None);

    let plan = plan_get("RFC-0001", Some("title"))?;
    assert!(matches!(plan.scope, Scope::Target { .. }));
    assert!(matches!(plan.op, Op::Get));
    assert_eq!(plan.lock_disposition(), LockDisposition::None);
    Ok(())
}

#[test]
fn test_lock_disposition_is_lock_free_for_inspect_commands()
-> Result<(), Box<dyn std::error::Error>> {
    assert_eq!(
        global(Op::Builtin(BuiltinOp::Status)).lock_disposition(),
        LockDisposition::None
    );
    assert_eq!(
        global(Op::Builtin(BuiltinOp::LoopList {
            filter: None,
            limit: None,
            output: OutputFormat::Table,
        }))
        .lock_disposition(),
        LockDisposition::None
    );
    assert_eq!(
        global(Op::Builtin(BuiltinOp::LoopShow {
            loop_id: "LOOP-2026-04-07-001".to_string(),
        }))
        .lock_disposition(),
        LockDisposition::None
    );
    assert_eq!(
        global(Op::Builtin(BuiltinOp::LoopResume {
            loop_id: "LOOP-2026-04-07-001".to_string(),
        }))
        .lock_disposition(),
        LockDisposition::None
    );
    assert_eq!(
        global(Op::Builtin(BuiltinOp::TagList {
            output: OutputFormat::Table,
        }))
        .lock_disposition(),
        LockDisposition::None
    );
    assert_eq!(
        global(Op::Builtin(BuiltinOp::Search {
            query: vec!["cache".to_string()],
            types: vec![],
            tags: vec![],
            limit: None,
            output: OutputFormat::Table,
            reindex: false,
        }))
        .lock_disposition(),
        LockDisposition::None
    );
    assert_eq!(
        plan_get("RFC-0001", Some("title"))?.lock_disposition(),
        LockDisposition::None
    );
    assert_eq!(
        plan_show(
            cmd::edit::ArtifactType::Adr,
            "ADR-0038",
            OutputFormat::Table
        )
        .lock_disposition(),
        LockDisposition::None
    );
    Ok(())
}

#[test]
fn test_lock_disposition_requires_lock_for_mutating_commands()
-> Result<(), Box<dyn std::error::Error>> {
    assert_eq!(
        global(Op::Builtin(BuiltinOp::Init { force: false })).lock_disposition(),
        LockDisposition::GovRootExclusive
    );
    assert_eq!(
        global(Op::Builtin(BuiltinOp::LoopRun {
            loop_id: "LOOP-2026-04-07-001".to_string(),
            target_work_ids: vec![],
            max_rounds: 1,
        }))
        .lock_disposition(),
        LockDisposition::GovRootExclusive
    );
    assert_eq!(
        global(Op::Builtin(BuiltinOp::LoopReplan {
            loop_id: "LOOP-2026-04-07-001".to_string(),
        }))
        .lock_disposition(),
        LockDisposition::GovRootExclusive
    );
    assert_eq!(
        plan_edit(
            "WI-2026-04-07-004",
            "acceptance_criteria[0]",
            tick_action(OwnedMatchOptions::default(), TickStatus::Done),
            EditExtras::default(),
        )?
        .lock_disposition(),
        LockDisposition::GovRootExclusive
    );
    assert_eq!(
        plan_lifecycle(
            cmd::edit::ArtifactType::WorkItem,
            "WI-2026-04-07-004",
            LifecycleOp::MoveWork {
                file_or_id: std::path::PathBuf::from("WI-2026-04-07-004"),
                status: WorkItemStatus::Done,
            },
        )
        .lock_disposition(),
        LockDisposition::GovRootExclusive
    );
    Ok(())
}

#[test]
fn test_self_update_is_lock_free() {
    assert_eq!(
        global(Op::Builtin(BuiltinOp::SelfUpdate { check: true })).lock_disposition(),
        LockDisposition::None
    );
    assert_eq!(
        global(Op::Builtin(BuiltinOp::SelfUpdate { check: false })).lock_disposition(),
        LockDisposition::None
    );
}