govctl 0.9.4

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

#[test]
fn test_self_update_routes_to_builtin_op() -> Result<(), Box<dyn std::error::Error>> {
    let check_plan = CommandPlan::from_parsed(&Commands::SelfUpdate { check: true }, false)?;
    assert!(matches!(check_plan.scope, Scope::Global));
    assert!(matches!(
        check_plan.op,
        Op::Builtin(BuiltinOp::SelfUpdate { check: true })
    ));

    let update_plan = CommandPlan::from_parsed(&Commands::SelfUpdate { check: false }, false)?;
    assert!(matches!(update_plan.scope, Scope::Global));
    assert!(matches!(
        update_plan.op,
        Op::Builtin(BuiltinOp::SelfUpdate { check: false })
    ));
    Ok(())
}

#[test]
fn test_target_resolves_get_and_edit_to_same_field_target() -> Result<(), Box<dyn std::error::Error>>
{
    let get = crate::AdrCommand::Get(crate::CommonGetArgs {
        id: "ADR-0038".to_string(),
        field: Some("alternatives[1].status".to_string()),
    })
    .to_plan()?;
    let edit = crate::AdrCommand::Edit(crate::AdrEditArgs {
        common: crate::CommonEditArgs {
            id: "ADR-0038".to_string(),
            path: "alternatives[1].status".to_string(),
            action: EditActionArgs {
                set: None,
                add: None,
                remove: None,
                tick: Some(TickStatus::Accepted),
                stdin: false,
                at: None,
                exact: false,
                regex: false,
                all: false,
            },
        },
        pro: vec![],
        con: vec![],
        reject_reason: None,
    })
    .to_plan()?;

    match ((&get.op, &get.scope), (&edit.op, &edit.scope)) {
        (
            (
                Op::Get,
                Scope::Target {
                    artifact: get_artifact,
                    id: get_id,
                    target: get_target,
                },
            ),
            (
                Op::Edit(EditOp::Field { .. }),
                Scope::Target {
                    artifact: edit_artifact,
                    id: edit_id,
                    target: edit_target,
                },
            ),
        ) => {
            assert_eq!(get_artifact, edit_artifact);
            assert_eq!(get_id, edit_id);
            assert_eq!(get_target, edit_target);
        }
        other => return Err(format!("expected field targets, got {other:?}").into()),
    }
    Ok(())
}

#[test]
fn test_work_tick_defaults_status_to_done() -> Result<(), Box<dyn std::error::Error>> {
    let cli = crate::Cli::parse_from([
        "govctl",
        "work",
        "tick",
        "WI-2026-04-07-001",
        "acceptance_criteria",
        "Criterion 1",
    ]);

    match cli.command {
        crate::Commands::Work {
            command: crate::WorkCommand::Tick(crate::WorkTickArgs { status, .. }),
        } => assert!(matches!(status, WorkTickStatus::Done)),
        _ => return Err("expected work tick command".into()),
    }
    Ok(())
}

#[test]
fn test_loop_commands_route_to_builtin_ops() -> Result<(), Box<dyn std::error::Error>> {
    let list_plan = CommandPlan::from_parsed(
        &Commands::Loop {
            command: crate::LoopCommand::List {
                filter: Some("open".to_string()),
                limit: Some(3),
                output: crate::OutputFormat::Json,
            },
        },
        false,
    )?;
    assert!(matches!(list_plan.scope, Scope::Global));
    assert!(matches!(
        list_plan.op,
        Op::Builtin(BuiltinOp::LoopList {
            filter: Some(ref filter),
            limit: Some(3),
            output: crate::OutputFormat::Json,
        }) if filter == "open"
    ));

    let run_plan = CommandPlan::from_parsed(
        &Commands::Loop {
            command: crate::LoopCommand::Run {
                id: "LOOP-2026-06-02-001".to_string(),
                max_rounds: 2,
                target_work_ids: vec!["WI-2026-06-02-058".to_string()],
            },
        },
        false,
    )?;
    assert!(matches!(
        run_plan.op,
        Op::Builtin(BuiltinOp::LoopRun {
            loop_id: ref id,
            target_work_ids: ref work,
            max_rounds: 2,
        }) if id == "LOOP-2026-06-02-001" && work == &vec!["WI-2026-06-02-058".to_string()]
    ));

    Ok(())
}

#[test]
fn test_tag_commands_route_to_builtin_ops() -> Result<(), Box<dyn std::error::Error>> {
    let new_plan = CommandPlan::from_parsed(
        &Commands::Tag {
            command: crate::TagCommand::New {
                tag: "cleanup".to_string(),
            },
        },
        false,
    )?;
    assert!(matches!(new_plan.scope, Scope::Global));
    assert!(matches!(
        new_plan.op,
        Op::Builtin(BuiltinOp::TagNew { ref tag }) if tag == "cleanup"
    ));

    let list_plan = CommandPlan::from_parsed(
        &Commands::Tag {
            command: crate::TagCommand::List {
                output: crate::OutputFormat::Plain,
            },
        },
        false,
    )?;
    assert!(matches!(
        list_plan.op,
        Op::Builtin(BuiltinOp::TagList {
            output: crate::OutputFormat::Plain,
        })
    ));

    Ok(())
}

#[test]
fn test_artifact_render_rejects_unsupported_artifacts() -> Result<(), Box<dyn std::error::Error>> {
    for (artifact, id) in [
        (cmd::edit::ArtifactType::Clause, "RFC-0001:C-SCOPE"),
        (cmd::edit::ArtifactType::Guard, "GUARD-CHECK"),
    ] {
        let plan = plan_artifact_render(artifact, id, false);
        let err = match plan.execute(
            &crate::config::Config::default(),
            crate::write::WriteOp::Execute,
        ) {
            Ok(_) => return Err("unsupported artifact render should fail".into()),
            Err(err) => err,
        };

        assert_eq!(err.code, DiagnosticCode::E0822UnsupportedOperation);
        assert_eq!(err.message, "render is not supported for this artifact");
        assert_eq!(err.file, id);
    }
    Ok(())
}