crates/gunnar-policy/tests/blind.rs
//! The permanent proof that `tests/gates.rs` can go red.
//!
//! # What this file is for
//!
//! The pre-receive gate's design asks for each gate to be *"deliberately stubbed to always-Allow"* and
//! its rejection test *"confirmed to go red"*. Doing that by hand proves the
//! assertion could fail on the day somebody did it, and proves nothing on any
//! day after.
//!
//! So it is done here instead, on every run, against the *same* fixtures:
//!
//! * [`AlwaysAllow`] stands in for each real gate. Every crafted bad push is
//! replayed through it, and the harness must come back `Err`. If it does not,
//! the assertion in `tests/gates.rs` was never capable of failing, and the
//! green there means nothing.
//! * [`AlwaysReject`] stands in for each gate in the allow direction, and the
//! harness must come back `Err` there too, because a gate that refuses
//! everything passes every rejection test in the suite and is just as broken.
//!
//! # What this file does NOT catch, checked by mutation rather than assumed
//!
//! It does not catch a gate that has gone blind, and it does not catch a fixture
//! that has stopped being a bad push. Both of those make the *real* gate allow,
//! and `tests/gates.rs` is what notices. Verified by mutating each in turn: the
//! gate stub and the softened fixture both leave this file green and take
//! `tests/gates.rs` red.
//!
//! What is left is the one failure `tests/gates.rs` structurally cannot see:
//! **an assertion that has stopped being able to fail.** A harness whose
//! `expect_reject` returned `Ok` unconditionally would leave every rejection
//! test in the crate green and worthless, and only a replay through a gate known
//! to allow can observe it. That mutation was applied too, and this file went
//! red with "16 of 16 rejection cases would pass".
mod common;
use common::{bad_pushes, good_pushes};
use gunnar_policy::blind::{AlwaysAllow, AlwaysReject};
#[test]
fn a_blind_gate_allows_every_crafted_bad_push_and_the_harness_says_so() {
let cases = bad_pushes();
let mut undetected = Vec::new();
for case in &cases {
let blind = AlwaysAllow::instead_of(case.gate.as_ref());
match (case.run)(&blind) {
Err(message) => {
// This is the point of the file: the harness noticed. Also check
// it noticed for the right reason, so a harness that always
// returns `Err` cannot pass either.
assert!(
message.contains("ALLOWED a push that must be refused"),
"case {:?}: the harness failed, but not because the blind gate allowed the \
push. It said: {message}",
case.name
);
}
Ok(verdict) => undetected.push(format!(
" {}: an always-allow gate satisfied this case (verdict {verdict:?}). \
Either the fixture is no longer a bad push, or the assertion cannot fail.",
case.name
)),
}
}
assert!(
undetected.is_empty(),
"{} of {} rejection cases would pass with the gate replaced by a stub that always \
allows, so those cases prove nothing:\n{}",
undetected.len(),
cases.len(),
undetected.join("\n")
);
}
#[test]
fn a_gate_that_refuses_everything_fails_the_allow_direction() {
let cases = good_pushes();
let mut undetected = Vec::new();
for case in &cases {
let blind = AlwaysReject::instead_of(case.gate.as_ref());
match (case.run)(&blind) {
Err(message) => assert!(
message.contains("REFUSED a push that must be allowed"),
"case {:?}: the harness failed for the wrong reason: {message}",
case.name
),
Ok(()) => undetected.push(format!(
" {}: an always-reject gate satisfied this case",
case.name
)),
}
}
assert!(
undetected.is_empty(),
"{} of {} allow cases would pass with the gate replaced by a stub that refuses \
everything:\n{}",
undetected.len(),
cases.len(),
undetected.join("\n")
);
}
#[test]
fn the_engine_treats_a_blind_gate_as_live_and_a_real_one_as_deciding() {
// The engine must not quietly special-case anything. A blind gate is a gate:
// it is listed, it runs, and it allows. The value of this assertion is that
// `gate_names()`, what `Policy.ListGates` answers with, is proved to
// reflect what actually runs.
use gunnar_policy::PolicyEngine;
let engine = PolicyEngine::new(vec![Box::new(AlwaysAllow::named("branch-protection"))]);
assert_eq!(engine.gate_names(), vec!["branch-protection"]);
assert_eq!(engine.len(), 1);
assert!(!engine.is_empty());
}