use std::{fs, path::Path};
use assert_cmd::Command;
use predicates::prelude::*;
fn write(path: &Path, contents: &str) {
fs::write(path, contents).unwrap();
}
#[test]
fn commit_msg_gate_accepts_evidenced_claim() {
let temp = tempfile::tempdir().unwrap();
let commit_msg = temp.path().join("COMMIT_EDITMSG");
write(
&commit_msg,
"feat: claim parser\n\nCLAIM: add parser | verified: cargo test | evidence: tests:cargo-test\n",
);
Command::cargo_bin("truth-mirror")
.unwrap()
.args(["gate", "--commit-msg", commit_msg.to_str().unwrap()])
.assert()
.success();
}
#[test]
fn commit_msg_gate_accepts_duplicate_empty_verification_aliases_in_any_order() {
let temp = tempfile::tempdir().unwrap();
let first = temp.path().join("FIRST_MSG");
let second = temp.path().join("SECOND_MSG");
write(
&first,
"feat: claim parser\n\nCLAIM: fix parser | verified: cargo test | verification: | evidence: tests:x\n",
);
write(
&second,
"feat: claim parser\n\nCLAIM: fix parser | verification: | verified: cargo test | evidence: tests:x\n",
);
for commit_msg in [first, second] {
Command::cargo_bin("truth-mirror")
.unwrap()
.args(["gate", "--commit-msg", commit_msg.to_str().unwrap()])
.assert()
.success();
}
}
#[test]
fn commit_msg_gate_rejects_missing_claim() {
let temp = tempfile::tempdir().unwrap();
let commit_msg = temp.path().join("COMMIT_EDITMSG");
write(&commit_msg, "feat: claim parser\n");
Command::cargo_bin("truth-mirror")
.unwrap()
.args(["gate", "--commit-msg", commit_msg.to_str().unwrap()])
.assert()
.failure()
.stderr(predicate::str::contains("missing CLAIM"));
}
#[test]
fn commit_msg_gate_rejects_claim_with_missing_evidence_directly() {
let temp = tempfile::tempdir().unwrap();
let commit_msg = temp.path().join("COMMIT_EDITMSG");
write(
&commit_msg,
"feat: done parser\n\nCLAIM: complete parser | verified: cargo test\n",
);
Command::cargo_bin("truth-mirror")
.unwrap()
.args(["gate", "--commit-msg", commit_msg.to_str().unwrap()])
.assert()
.failure()
.stderr(predicate::str::contains("missing evidence pointer"));
}
#[test]
fn commit_msg_gate_rejects_completion_without_any_claim() {
let temp = tempfile::tempdir().unwrap();
let commit_msg = temp.path().join("COMMIT_EDITMSG");
write(&commit_msg, "feat: done — this is complete\n");
Command::cargo_bin("truth-mirror")
.unwrap()
.args(["gate", "--commit-msg", commit_msg.to_str().unwrap()])
.assert()
.failure()
.stderr(predicate::str::contains(
"completion wording lacks evidence",
))
.stderr(predicate::str::contains("example: CLAIM: change behavior"));
}
#[test]
fn pre_push_warns_about_queued_unreviewed_claims_without_blocking() {
let temp = tempfile::tempdir().unwrap();
let state = temp.path().join("state");
fs::create_dir(&state).unwrap();
fs::write(
state.join("review-queue.jsonl"),
"{\"commit_sha\":\"abc123\",\"enqueued_at_unix\":1}\n",
)
.unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.args([
"--state-dir",
state.to_str().unwrap(),
"gate",
"--pre-push",
"abc123",
])
.assert()
.success()
.stderr(predicate::str::contains("1 unreviewed claim(s) queued"))
.stderr(predicate::str::contains("truth-mirror --state-dir"))
.stderr(predicate::str::contains(state.to_string_lossy().as_ref()))
.stderr(predicate::str::contains("watch --once"));
}
#[test]
fn truth_alias_pre_push_warning_uses_alias_prefix_and_drain_command() {
let temp = tempfile::tempdir().unwrap();
let state = temp.path().join("state");
fs::create_dir(&state).unwrap();
fs::write(
state.join("review-queue.jsonl"),
"{\"commit_sha\":\"abc123\",\"enqueued_at_unix\":1}\n",
)
.unwrap();
Command::cargo_bin("truth")
.unwrap()
.args([
"--state-dir",
state.to_str().unwrap(),
"gate",
"--pre-push",
"abc123",
])
.assert()
.success()
.stderr(predicate::str::contains("truth: warning"))
.stderr(predicate::str::contains("truth --state-dir"))
.stderr(predicate::str::contains("truth-mirror: warning").not());
}
#[test]
fn pre_push_warning_preserves_config_in_drain_guidance() {
let temp = tempfile::tempdir().unwrap();
let state = temp.path().join("state");
let config = temp.path().join("truth.toml");
fs::create_dir(&state).unwrap();
fs::write(&config, "").unwrap();
fs::write(
state.join("review-queue.jsonl"),
"{\"commit_sha\":\"abc123\",\"enqueued_at_unix\":1}\n",
)
.unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.args([
"--config",
config.to_str().unwrap(),
"--state-dir",
state.to_str().unwrap(),
"gate",
"--pre-push",
"abc123",
])
.assert()
.success()
.stderr(predicate::str::contains("truth-mirror --config"))
.stderr(predicate::str::contains(config.to_string_lossy().as_ref()))
.stderr(predicate::str::contains("watch --once"));
}
#[test]
fn pre_push_queue_warning_parse_error_does_not_block_push() {
let temp = tempfile::tempdir().unwrap();
let state = temp.path().join("state");
fs::create_dir(&state).unwrap();
fs::write(state.join("review-queue.jsonl"), "not json\n").unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.args([
"--state-dir",
state.to_str().unwrap(),
"gate",
"--pre-push",
"abc123",
])
.assert()
.success()
.stderr(predicate::str::contains(
"could not read async review queue",
));
}
#[test]
fn commit_msg_gate_rejects_fake_marker_in_diff_file() {
let temp = tempfile::tempdir().unwrap();
let commit_msg = temp.path().join("COMMIT_EDITMSG");
let diff = temp.path().join("staged.diff");
write(
&commit_msg,
"feat: claim parser\n\nCLAIM: add parser | verified: cargo test | evidence: tests:cargo-test\n",
);
write(
&diff,
"diff --git a/src/lib.rs b/src/lib.rs\n+ TODO-as-done\n",
);
Command::cargo_bin("truth-mirror")
.unwrap()
.args([
"gate",
"--commit-msg",
commit_msg.to_str().unwrap(),
"--diff-file",
diff.to_str().unwrap(),
])
.assert()
.failure()
.stderr(predicate::str::contains("TODO-as-done"));
}