mod common;
use common::{Repo, PROSE, STANDARDS};
const CLEAN: &str = "VERDICT: reviewer=fake session=s-01 major=0 moderate=1 minor=2";
const BLOCKER: &str = "VERDICT: reviewer=fake session=s-02 major=1 moderate=0 minor=0";
const AIM: &str = "raise the staged file's line count";
#[test]
fn the_brief_names_the_staged_files_in_scope() {
let repo = Repo::new();
repo.write("other.rs", "more");
repo.declare(CLEAN, &[STANDARDS]);
repo.stage(&["src.rs"]);
let run = repo.capture(&["--reviewer-prompt", "standards"]);
assert_eq!(run.code, 0, "{}", run.err);
assert!(run.out.contains("files under review"), "{}", run.out);
assert!(run.out.contains("src.rs"), "{}", run.out);
assert!(!run.out.contains("other.rs"), "{}", run.out);
}
#[test]
fn the_trailer_carries_the_counts_the_reviewer_reported() {
let repo = Repo::new();
repo.declare(CLEAN, &[STANDARDS]);
repo.stage(&["src.rs"]);
let message = repo.landed(AIM, 3);
assert!(message.contains("Reviewed-standards:"), "{message}");
assert!(message.contains("major=0 moderate=1 minor=2"), "{message}");
assert!(message.contains("reviewer=fake"), "{message}");
}
#[test]
fn the_intent_becomes_the_subject() {
let repo = Repo::new();
repo.declare(CLEAN, &[STANDARDS]);
repo.stage(&["src.rs"]);
let message = repo.landed(AIM, 3);
assert_eq!(message.lines().next(), Some(AIM), "{message}");
}
#[test]
fn a_major_holds_the_gate_and_commits_nothing() {
let repo = Repo::new();
repo.declare(BLOCKER, &[STANDARDS]);
repo.stage(&["src.rs"]);
let run = repo.attest(AIM);
assert_eq!(run.code, 1, "{}", run.err);
assert!(run.err.contains("MAJOR"), "{}", run.err);
assert!(!repo.committed(), "a blocked gate committed anyway");
}
#[test]
fn a_blocked_gate_is_the_one_that_runs_again() {
let repo = Repo::new();
repo.declare(BLOCKER, &[STANDARDS, PROSE]);
repo.stage(&["src.rs"]);
for _ in 0..2 {
let run = repo.attest(AIM);
assert!(run.err.contains("standards:"), "{}", run.err);
assert!(!run.err.contains("prose:"), "{}", run.err);
}
}
#[test]
fn gates_are_reviewed_in_the_order_the_hook_declares_them() {
let repo = Repo::new();
let second_gate = r#""$1" ann --doc rubric.md --path ."#;
repo.declare(CLEAN, &[STANDARDS, second_gate]);
repo.stage(&["src.rs"]);
let first = repo.attest(AIM);
assert!(first.err.contains("standards:"), "{}", first.err);
let second = repo.attest(AIM);
assert!(second.err.contains("ann:"), "{}", second.err);
let message = repo.landed(AIM, 2);
assert!(message.contains("Reviewed-standards:"), "{message}");
assert!(message.contains("Reviewed-ann:"), "{message}");
}
#[test]
fn an_advisory_gate_counts_findings_and_never_grades() {
let repo = Repo::new();
repo.declare("VERDICT: reviewer=fake session=s-03 findings=4", &[PROSE]);
repo.stage(&["src.rs"]);
let message = repo.landed(AIM, 3);
assert!(message.contains("Reviewed-prose:"), "{message}");
assert!(message.contains("findings=4"), "{message}");
assert!(!message.contains("major="), "{message}");
}
#[test]
fn content_moving_after_its_verdict_is_named_before_the_commit() {
let repo = Repo::new();
repo.declare(CLEAN, &[STANDARDS]);
repo.stage(&["src.rs"]);
repo.attest(AIM);
repo.write("src.rs", "code, and the line the fix added");
repo.stage(&["src.rs"]);
let run = repo.attest(AIM);
assert!(repo.committed(), "{}", run.err);
assert!(
run.err.contains("CONTENT MOVED SINCE ITS VERDICT"),
"{}",
run.err
);
assert!(run.err.contains("standards"), "{}", run.err);
}
#[test]
fn content_left_alone_after_its_verdict_says_nothing() {
let repo = Repo::new();
repo.declare(CLEAN, &[STANDARDS]);
repo.stage(&["src.rs"]);
let run = repo.attest_until(AIM, 3);
assert!(repo.committed(), "{}", run.err);
assert!(!run.err.contains("CONTENT MOVED"), "{}", run.err);
}
#[test]
fn a_gate_reviews_the_same_files_from_any_directory() {
let repo = Repo::new();
std::fs::create_dir_all(repo.dir.join("sub")).expect("subdir");
repo.write("sub/deep.rs", "deep");
repo.declare(CLEAN, &[STANDARDS]);
repo.stage(&["src.rs", "sub/deep.rs"]);
let from_root = repo.capture(&["--reviewer-prompt", "standards"]);
let from_sub = repo.capture_in("sub", &["--reviewer-prompt", "standards"]);
assert_eq!(from_sub.code, 0, "{}", from_sub.err);
assert_eq!(from_root.out, from_sub.out, "{}", from_sub.out);
assert!(from_sub.out.contains("src.rs"), "{}", from_sub.out);
}
#[test]
fn a_staged_rubric_still_lets_attest_read_the_hook() {
let repo = Repo::new();
let guard = r#"--rubric-guard --doc rubric.md"#;
repo.declare(CLEAN, &[guard, STANDARDS]);
repo.stage(&["src.rs", "rubric.md"]);
let run = repo.attest(AIM);
assert!(!run.err.contains("declared no gates"), "{}", run.err);
assert!(run.err.contains("RUBRIC IS STAGED"), "{}", run.err);
assert!(!repo.committed(), "a staged rubric committed anyway");
}
#[test]
fn the_landed_commit_is_announced_on_stdout() {
let repo = Repo::new();
repo.declare(CLEAN, &[STANDARDS]);
repo.stage(&["src.rs"]);
let run = repo.attest_until(AIM, 3);
assert!(repo.committed(), "{}", run.err);
assert!(run.out.contains("committed "), "{}", run.out);
assert!(run.out.contains("nothing left to run"), "{}", run.out);
}
#[test]
fn attest_after_the_commit_landed_names_the_empty_index() {
let repo = Repo::new();
repo.declare(CLEAN, &[STANDARDS]);
repo.stage(&["src.rs"]);
repo.attest_until(AIM, 3);
assert!(repo.committed());
let run = repo.attest(AIM);
assert_eq!(run.code, 2, "{}", run.err);
assert!(run.err.contains("nothing staged"), "{}", run.err);
}
#[test]
fn a_graded_and_an_advisory_gate_land_their_own_shapes_in_one_commit() {
let repo = Repo::new();
let per_gate = r#"case "$(cat)" in *"gate: prose"*) printf 'VERDICT: reviewer=fake session=s-1 findings=3\n';; *) printf 'VERDICT: reviewer=fake session=s-2 major=0 moderate=1 minor=2\n';; esac"#;
repo.declare_runner(per_gate, &[STANDARDS, PROSE]);
repo.stage(&["src.rs"]);
let message = repo.landed(AIM, 3);
let graded = "Reviewed-standards: reviewer=fake major=0 moderate=1 minor=2";
assert!(message.contains(graded), "{message}");
assert!(
message.contains("Reviewed-prose: reviewer=fake findings=3"),
"{message}"
);
}
#[test]
fn the_intent_cannot_change_between_gates() {
let repo = Repo::new();
repo.declare(CLEAN, &[STANDARDS, PROSE]);
repo.stage(&["src.rs"]);
repo.attest(AIM);
let run = repo.attest("something else entirely");
assert_eq!(run.code, 2, "{}", run.err);
assert!(run.err.contains("only change after a MAJOR"), "{}", run.err);
}
#[test]
fn an_intent_naming_more_than_one_change_is_refused_with_the_remedy() {
let repo = Repo::new();
repo.declare(CLEAN, &[STANDARDS]);
repo.stage(&["src.rs"]);
let two_changes = "x".repeat(301);
let run = repo.capture(&["attest", "--intent", &two_changes]);
assert_eq!(run.code, 2, "{}", run.err);
assert!(run.err.contains("commit them separately"), "{}", run.err);
}
#[test]
fn a_reviewer_that_refuses_the_brief_records_nothing() {
let repo = Repo::new();
repo.declare("VERDICT: refused", &[STANDARDS]);
repo.stage(&["src.rs"]);
let run = repo.attest("add the cache because the old path was far too slow");
assert_eq!(run.code, 2, "{}", run.err);
assert!(run.err.contains("refused the brief"), "{}", run.err);
assert!(!repo.committed(), "a refused brief committed anyway");
}
#[test]
fn an_advisory_gate_refuses_a_contaminated_brief_too() {
let repo = Repo::new();
repo.declare("VERDICT: refused", &[PROSE]);
repo.stage(&["src.rs"]);
let run = repo.attest("add the cache because the old path was far too slow");
assert_eq!(run.code, 2, "{}", run.err);
assert!(run.err.contains("refused the brief"), "{}", run.err);
}
#[test]
fn a_verdict_line_missing_a_required_field_is_refused() {
for missing in [
"VERDICT: session=s-1 major=0 moderate=0 minor=0",
"VERDICT: reviewer=fake major=0 moderate=0 minor=0",
] {
let repo = Repo::new();
repo.declare(missing, &[STANDARDS]);
repo.stage(&["src.rs"]);
let run = repo.attest(AIM);
assert_eq!(run.code, 2, "{missing}: {}", run.err);
assert!(run.err.contains("carries no"), "{missing}: {}", run.err);
}
}
#[test]
fn a_reviewer_closing_with_two_verdict_lines_is_refused() {
let repo = Repo::new();
let doubled = "VERDICT: reviewer=a session=s-1 major=0 moderate=1 minor=0\\nVERDICT: reviewer=b session=s-2 major=0 moderate=2 minor=0";
repo.declare(doubled, &[STANDARDS]);
repo.stage(&["src.rs"]);
let run = repo.attest(AIM);
assert_eq!(run.code, 2, "{}", run.err);
assert!(run.err.contains("asks for one"), "{}", run.err);
assert!(!repo.committed(), "a doubled verdict committed anyway");
}
#[test]
fn what_the_reviewer_said_reaches_the_author() {
let repo = Repo::new();
let spoken = format!("MODERATE - the lede repeats the heading\\n{CLEAN}");
repo.declare(&spoken, &[STANDARDS]);
repo.stage(&["src.rs"]);
let run = repo.attest(AIM);
let path = run
.err
.lines()
.find_map(|l| l.strip_prefix("see the full report: "))
.expect("a path")
.trim()
.to_string();
let body = std::fs::read_to_string(&path).expect("the report");
assert!(body.contains("the lede repeats the heading"), "{body}");
let _ = std::fs::remove_file(&path);
}
#[test]
fn a_reviewer_that_reports_no_verdict_line_is_an_error_not_a_pass() {
let repo = Repo::new();
repo.declare("no verdict here", &[STANDARDS]);
repo.stage(&["src.rs"]);
let run = repo.attest(AIM);
assert_eq!(run.code, 2, "{}", run.err);
assert!(run.err.contains("VERDICT"), "{}", run.err);
}
#[test]
fn a_host_with_no_reviewer_configured_says_so() {
let repo = Repo::new();
repo.hook(&[STANDARDS]);
repo.stage(&["src.rs"]);
let run = repo.attest(AIM);
assert_eq!(run.code, 2, "{}", run.err);
assert!(run.err.contains("agent-verdict.runner"), "{}", run.err);
}
#[test]
fn a_reset_is_counted_and_keeps_its_reason() {
let repo = Repo::new();
repo.declare(CLEAN, &[STANDARDS]);
repo.stage(&["src.rs"]);
repo.attest(AIM);
let run = repo.capture(&["reset", "the", "brief", "named", "the", "wrong", "aim"]);
assert_eq!(run.code, 0, "{}", run.err);
assert!(run.err.contains("reset 1"), "{}", run.err);
let message = repo.landed("a different aim entirely", 3);
assert!(message.contains("resets=1"), "{message}");
assert!(message.contains("Reset: the brief named"), "{message}");
}
#[test]
fn a_reset_without_a_reason_is_refused() {
let repo = Repo::new();
repo.declare(CLEAN, &[STANDARDS]);
let run = repo.capture(&["reset"]);
assert_eq!(run.code, 2, "{}", run.err);
assert!(run.err.contains("needs a reason"), "{}", run.err);
}
#[test]
fn a_long_report_is_written_where_it_can_be_read_whole() {
let repo = Repo::new();
let long: String = (1..=60)
.map(|n| format!("MINOR - finding {n}\\n"))
.collect();
repo.declare(&format!("{long}{CLEAN}"), &[STANDARDS]);
repo.stage(&["src.rs"]);
let run = repo.attest(AIM);
assert!(run.out.contains("standards: major=0"), "{}", run.out);
assert!(!run.err.contains("finding 42"), "{}", run.err);
let path = run
.err
.lines()
.find_map(|l| l.strip_prefix("see the full report: "))
.expect("a path")
.trim()
.to_string();
let body = std::fs::read_to_string(&path).expect("the report");
assert!(body.contains("finding 42"), "{body}");
assert!(body.contains("finding 60"), "{body}");
let _ = std::fs::remove_file(&path);
}