use crate::brain::tools::plan_tool::{
extract_sha_claims, receipt_binding_dir, validate_plan_working_directory, verify_sha_receipts,
};
use std::path::{Path, PathBuf};
use std::process::Command;
#[test]
fn extracts_sha_after_committed() {
let claims = extract_sha_claims("Committed 7c1856c9: extracted classify_codex_failure");
assert_eq!(claims, vec!["7c1856c9"]);
}
#[test]
fn extracts_across_commit_push_sha_keywords() {
assert_eq!(
extract_sha_claims("commit abc1234567 done"),
vec!["abc1234567"]
);
assert_eq!(
extract_sha_claims("pushed deadbeef01 to origin"),
vec!["deadbeef01"]
);
assert_eq!(
extract_sha_claims("sha cafebabe1234 verified"),
vec!["cafebabe1234"]
);
}
#[test]
fn keyword_matching_is_case_insensitive() {
assert_eq!(
extract_sha_claims("COMMITTED ABCDEF1234"),
vec!["ABCDEF1234"]
);
}
#[test]
fn ignores_hex_without_commit_context() {
assert!(extract_sha_claims("error at deadbeef12345 in module").is_empty());
assert!(extract_sha_claims("call_eb7512b1c56a42f3a8d59009 accumulated").is_empty());
}
#[test]
fn ignores_file_hash_digests() {
let text = "sha256 b23d70de9379c05b4b1ee2b7e1129dc2f41f45e0bf1d2c88c7a9b21c7d6e6a1f";
assert!(extract_sha_claims(text).is_empty());
assert!(extract_sha_claims("sha256 b23d70de matches the artifact").is_empty());
}
#[test]
fn ignores_hex_shorter_than_git_abbrev() {
assert!(extract_sha_claims("commit abc123").is_empty());
}
#[test]
fn dedupes_repeated_claims_case_insensitively() {
let claims = extract_sha_claims("Committed 7c1856c9 and pushed 7C1856C9 to origin.");
assert_eq!(claims, vec!["7c1856c9"]);
}
#[test]
fn empty_output_has_no_claims() {
assert!(extract_sha_claims("").is_empty());
}
#[test]
fn multibyte_context_does_not_panic_the_window() {
let text = "ação ção ééé committed 7c1856c9 done";
assert_eq!(extract_sha_claims(text), vec!["7c1856c9"]);
assert!(extract_sha_claims("éééééééééééééé 7c1856c9").is_empty());
}
#[test]
fn signature_algorithm_tokens_are_not_claims() {
let text = "Committed as bfe9e032, signed (ED25519, SHA256:nZH20uL)";
assert_eq!(extract_sha_claims(text), vec!["bfe9e032"]);
}
#[test]
fn signature_token_exclusion_is_case_insensitive() {
assert!(extract_sha_claims("commit ed25519 here").is_empty());
assert!(extract_sha_claims("commit ED25519 here").is_empty());
}
fn git(repo: &Path, args: &[&str]) {
let out = Command::new("git")
.arg("-C")
.arg(repo)
.args(args)
.output()
.expect("git available");
assert!(
out.status.success(),
"git {:?} failed in {}: {}",
args,
repo.display(),
String::from_utf8_lossy(&out.stderr).trim()
);
}
fn fixture_repo_with_content(content: &[u8]) -> (tempfile::TempDir, String) {
let dir = tempfile::tempdir().expect("tmpdir");
let repo = dir.path();
git(repo, &["init", "-q"]);
git(repo, &["config", "user.email", "receipt@test.local"]);
git(repo, &["config", "user.name", "Receipt Test"]);
std::fs::write(repo.join("fixture.txt"), content).expect("write fixture");
git(repo, &["add", "fixture.txt"]);
git(repo, &["commit", "-q", "-m", "fixture commit"]);
let out = Command::new("git")
.arg("-C")
.arg(repo)
.args(["rev-parse", "HEAD"])
.output()
.expect("rev-parse");
let sha = String::from_utf8_lossy(&out.stdout).trim().to_string();
(dir, sha)
}
fn fixture_repo_with_commit() -> (tempfile::TempDir, String) {
fixture_repo_with_content(b"receipt")
}
#[test]
fn real_sha_claim_passes() {
let (dir, sha) = fixture_repo_with_commit();
let output = format!("Committed {sha}: fixture work");
assert!(verify_sha_receipts(&output, dir.path()).is_ok());
}
#[test]
fn real_short_prefix_claim_passes() {
let (dir, sha) = fixture_repo_with_commit();
let abbrev = &sha[..8];
let output = format!("Committed {abbrev}: fixture work");
assert!(verify_sha_receipts(&output, dir.path()).is_ok());
}
#[test]
fn phantom_sha_claim_is_rejected_with_evidence() {
let (dir, _sha) = fixture_repo_with_commit();
let phantom = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
let output = format!("Committed {phantom}: did the work");
let err = verify_sha_receipts(&output, dir.path()).expect_err("phantom sha must reject");
assert!(err.contains(phantom), "{err}");
assert!(err.contains("#1011"), "{err}");
}
#[test]
fn unknown_short_prefix_is_rejected() {
let (dir, _sha) = fixture_repo_with_commit();
let err = verify_sha_receipts("Committed deadbee: done", dir.path())
.expect_err("unknown prefix must reject");
assert!(err.contains("deadbee"), "{err}");
}
#[test]
fn non_git_dir_skips_the_check() {
let dir = tempfile::tempdir().expect("tmpdir");
let output = "Committed deadbeefdeadbeefdeadbeefdeadbeefdeadbeef: done";
assert!(verify_sha_receipts(output, dir.path()).is_ok());
}
#[test]
fn output_without_claims_passes_in_a_repo() {
let (dir, _sha) = fixture_repo_with_commit();
assert!(verify_sha_receipts("Did the work, tests green", dir.path()).is_ok());
}
#[test]
fn binding_dir_prefers_the_plan_binding() {
let resolved = receipt_binding_dir(Some("/plan/repo"), Path::new("/session/cwd"));
assert_eq!(resolved, PathBuf::from("/plan/repo"));
}
#[test]
fn binding_dir_falls_back_to_session_cwd_without_a_binding() {
let resolved = receipt_binding_dir(None, Path::new("/session/cwd"));
assert_eq!(resolved, PathBuf::from("/session/cwd"));
}
#[test]
fn binding_dir_ignores_a_blank_binding() {
let resolved = receipt_binding_dir(Some(" "), Path::new("/session/cwd"));
assert_eq!(resolved, PathBuf::from("/session/cwd"));
}
#[test]
fn cross_repo_sha_verifies_against_the_plan_binding() {
let (repo_a, _sha_a) = fixture_repo_with_content(b"session repo work");
let (repo_b, sha_b) = fixture_repo_with_content(b"plan repo work");
let output = format!("Committed {sha_b}: did the work in the plan's repo");
assert!(
verify_sha_receipts(&output, repo_a.path()).is_err(),
"sha_b must not exist in repo_a — if this fails the fixtures collided"
);
let receipt_dir = receipt_binding_dir(
Some(repo_b.path().to_str().expect("utf8 tempdir")),
repo_a.path(),
);
assert!(verify_sha_receipts(&output, &receipt_dir).is_ok());
}
#[test]
fn validate_working_directory_rejects_relative_paths() {
assert!(validate_plan_working_directory("srv/rs/bolina-rs").is_err());
assert!(validate_plan_working_directory("./bolina-rs").is_err());
}
#[test]
fn validate_working_directory_rejects_missing_dirs() {
assert!(validate_plan_working_directory("/definitely/not/a/real/dir_1452").is_err());
}
#[test]
fn validate_working_directory_accepts_an_existing_dir() {
let dir = tempfile::tempdir().expect("tmpdir");
let path = dir.path().to_str().expect("utf8 tempdir");
assert!(validate_plan_working_directory(path).is_ok());
}
#[test]
fn wiring_complete_passes_the_resolved_binding_dir() {
let source = include_str!("../brain/tools/plan_tool.rs");
assert!(
source.contains("receipt_binding_dir("),
"complete must resolve the verification repo via receipt_binding_dir (#1452)"
);
assert!(
source.contains("current_plan.working_directory.as_deref()"),
"complete must consult the plan's working_directory for the receipt binding (#1452)"
);
}