gy 1.2.1

good,yes — the ledger of what work rests on before a requirement is confirmed
mod common;

use common::{fixture, id_of, stderr, stdout};
use gy_ledger::{Node, NodeId, NodeKind};

fn criterion(hash: &str) -> Node {
    Node::criterion(
        NodeId::from_hash(NodeKind::Criterion, hash).unwrap(),
        "a",
        "2026-09-15T00:00:00Z",
        "a criterion",
    )
    .unwrap()
}

#[test]
fn need_add_then_close() {
    let fx = fixture();
    fx.seed(&[criterion("0001")]);

    let out = fx.run(&["need", "add", "a need", "--targets", "ac-0001"]);
    assert!(out.status.success(), "{}", stderr(&out));
    assert!(stdout(&out).contains("changed: created, targets"));
    assert!(stdout(&out).contains("missing: a body, a filed-as requirement"));
    let id = id_of(&out);

    let out = fx.run(&[
        "need",
        "close",
        &id,
        "--by",
        "fact",
        "--evidence",
        "resolved",
    ]);
    assert!(out.status.success(), "{}", stderr(&out));
    assert!(stdout(&out).contains("changed: closed"));

    let out = fx.run(&["need", "close", &id, "--by", "fact", "--evidence", "again"]);
    assert_eq!(out.status.code(), Some(2));

    let out = fx.run(&["show", &id]);
    assert!(stdout(&out).contains("state: closed"));
}

#[test]
fn need_close_names_the_valid_by_values() {
    let fx = fixture();
    fx.seed(&[criterion("0001")]);
    let id = id_of(&fx.run(&["need", "add", "a need", "--targets", "ac-0001"]));

    let out = fx.run(&["need", "close", &id, "--by", "nope", "--evidence", "e"]);
    assert_eq!(out.status.code(), Some(2));
    let message = stderr(&out);
    assert!(message.contains("expected one of"), "{message}");
    for word in ["fact", "external"] {
        assert!(message.contains(word), "{message}");
    }
}

#[test]
fn need_close_help_names_the_by_values() {
    let fx = fixture();
    let help = fx.run(&["need", "close", "--help"]);
    assert!(help.status.success(), "{}", stderr(&help));
    let text = stdout(&help);
    for word in ["fact", "external"] {
        assert!(text.contains(word), "{text}");
    }
}

#[test]
fn need_add_usage_shows_the_repeated_form() {
    let fx = fixture();
    let out = fx.run(&["need", "add", "t", "--targets", "ac-0001", "ac-0002"]);
    assert_eq!(out.status.code(), Some(2));
    assert!(
        stderr(&out).contains("--targets <AC> [--targets <AC>]..."),
        "{}",
        stderr(&out)
    );
}

#[test]
fn need_errors_and_json() {
    let fx = fixture();
    fx.seed(&[criterion("0001")]);

    let out = fx.run(&["--json", "need", "add", "a need", "--targets", "ac-0001"]);
    assert!(out.status.success(), "{}", stderr(&out));
    let json: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
    assert!(json["id"].is_string());
    assert_eq!(json["changed"][0], "created");

    let out = fx.run(&["need", "add", "a need", "--targets", "ac-9999"]);
    assert_eq!(out.status.code(), Some(2));

    let out = fx.run_without_actor(&["need", "add", "a need", "--targets", "ac-0001"]);
    assert_eq!(out.status.code(), Some(2));
    assert!(stderr(&out).contains("GY_ACTOR"));
}