use super::*;
use crate::StrategyOutcomeKind;
fn inapplicable_finding(block_family: Option<BlockFamily>, reason: &str) -> ContributingFinding {
ContributingFinding {
strategy_id: "test-strategy".to_owned(),
strategy_name: "Test Strategy".to_owned(),
outcome_kind: StrategyOutcomeKind::Inapplicable,
log_odds_contribution: 0.0,
block_family,
block_reason: Some(reason.to_owned()),
}
}
fn contradictory_finding() -> ContributingFinding {
ContributingFinding {
strategy_id: "test-strategy".to_owned(),
strategy_name: "Test Strategy".to_owned(),
outcome_kind: StrategyOutcomeKind::Contradictory,
log_odds_contribution: -0.2,
block_family: None,
block_reason: None,
}
}
fn positive_finding() -> ContributingFinding {
ContributingFinding {
strategy_id: "test-strategy".to_owned(),
strategy_name: "Test Strategy".to_owned(),
outcome_kind: StrategyOutcomeKind::Positive,
log_odds_contribution: 0.75,
block_family: None,
block_reason: None,
}
}
fn no_signal_finding() -> ContributingFinding {
ContributingFinding {
strategy_id: "test-strategy".to_owned(),
strategy_name: "Test Strategy".to_owned(),
outcome_kind: StrategyOutcomeKind::NoSignal,
log_odds_contribution: 0.0,
block_family: None,
block_reason: None,
}
}
#[test]
fn evidence_bearing_returns_evidence_observed() {
let findings = vec![positive_finding()];
let (status, summary) = compute_observability(&findings);
assert_eq!(status, ObservabilityStatus::EvidenceObserved);
assert!(summary.is_none());
}
#[test]
fn contradictory_returns_evidence_observed() {
let findings = vec![contradictory_finding(), contradictory_finding()];
let (status, _) = compute_observability(&findings);
assert_eq!(status, ObservabilityStatus::EvidenceObserved);
}
#[test]
fn fewer_than_3_opportunities_returns_underpowered() {
let findings = vec![
inapplicable_finding(
Some(BlockFamily::Authorization),
"auth gate fired before technique (no credential provided)",
),
inapplicable_finding(
Some(BlockFamily::Authorization),
"auth gate fired before technique (no credential provided)",
),
];
let (status, _) = compute_observability(&findings);
assert_eq!(status, ObservabilityStatus::Underpowered);
}
#[test]
fn all_auth_blocked_returns_blocked_before_oracle_layer() {
let findings: Vec<_> = (0..8)
.map(|_| {
inapplicable_finding(
Some(BlockFamily::Authorization),
"auth gate fired before technique (no credential provided)",
)
})
.collect();
let (status, summary) = compute_observability(&findings);
assert_eq!(status, ObservabilityStatus::BlockedBeforeOracleLayer);
let s = summary.expect("summary must be present for BlockedBeforeOracleLayer");
assert_eq!(s.dominant_block_family, BlockFamily::Authorization);
assert!(s.blocked_fraction >= 0.80);
assert!(
s.operator_action
.as_deref()
.unwrap_or("")
.contains("Authorization: Bearer"),
"operator_action must mention Authorization: Bearer"
);
}
#[test]
fn all_no_signal_returns_probed_no_evidence() {
let mut findings: Vec<ContributingFinding> = (0..8)
.map(|_| {
inapplicable_finding(
Some(BlockFamily::TechniqueLocal),
"applicability marker not observed",
)
})
.collect();
findings.extend((0..2).map(|_| no_signal_finding()));
let (status, _) = compute_observability(&findings);
assert_eq!(status, ObservabilityStatus::ProbedNoEvidence);
}
#[test]
fn mixed_blocked_returns_partially_blocked() {
let blocked: Vec<_> = (0..4)
.map(|_| {
inapplicable_finding(
Some(BlockFamily::Authorization),
"auth gate fired before technique (no credential provided)",
)
})
.collect();
let local: Vec<_> = (0..4)
.map(|_| {
inapplicable_finding(
Some(BlockFamily::TechniqueLocal),
"applicability marker not observed",
)
})
.collect();
let findings: Vec<_> = blocked.into_iter().chain(local).collect();
let (status, summary) = compute_observability(&findings);
assert_eq!(status, ObservabilityStatus::PartiallyBlocked);
assert!(summary.is_some());
}
#[test]
fn no_findings_returns_underpowered() {
let (status, _) = compute_observability(&[]);
assert_eq!(status, ObservabilityStatus::Underpowered);
}
#[test]
fn method_blocked_returns_blocked_before_oracle_layer() {
let findings: Vec<_> = (0..5)
.map(|_| {
inapplicable_finding(
Some(BlockFamily::Method),
"method-level rejection before resource lookup",
)
})
.collect();
let (status, summary) = compute_observability(&findings);
assert_eq!(status, ObservabilityStatus::BlockedBeforeOracleLayer);
let s = summary.unwrap();
assert_eq!(s.dominant_block_family, BlockFamily::Method);
assert!(s
.operator_action
.as_deref()
.unwrap_or("")
.contains("HTTP method"));
}
#[test]
fn blocked_fraction_is_clamped_to_unit_interval() {
let findings: Vec<_> = (0..10)
.map(|_| {
inapplicable_finding(
Some(BlockFamily::Authorization),
"auth gate fired before technique (no credential provided)",
)
})
.collect();
let (_, summary) = compute_observability(&findings);
let s = summary.unwrap();
assert!((0.0..=1.0).contains(&s.blocked_fraction));
}
use proptest::prelude::*;
proptest! {
#[test]
fn evidence_bearing_always_yields_evidence_observed(
inapplicable_count in 0usize..20usize,
) {
let mut findings: Vec<ContributingFinding> = (0..inapplicable_count)
.map(|_| inapplicable_finding(
Some(BlockFamily::Authorization),
"auth gate fired before technique (no credential provided)",
))
.collect();
findings.push(positive_finding());
let (status, _) = compute_observability(&findings);
prop_assert_eq!(
status,
ObservabilityStatus::EvidenceObserved,
"presence of positive finding must yield EvidenceObserved"
);
}
#[test]
fn block_summary_fraction_is_in_unit_interval(
auth_count in 0usize..20usize,
method_count in 0usize..20usize,
local_count in 0usize..20usize,
) {
let mut findings = Vec::new();
for _ in 0..auth_count {
findings.push(inapplicable_finding(
Some(BlockFamily::Authorization),
"auth gate fired before technique (no credential provided)",
));
}
for _ in 0..method_count {
findings.push(inapplicable_finding(
Some(BlockFamily::Method),
"method-level rejection before resource lookup",
));
}
for _ in 0..local_count {
findings.push(inapplicable_finding(
Some(BlockFamily::TechniqueLocal),
"applicability marker not observed",
));
}
let (_, summary) = compute_observability(&findings);
if let Some(s) = summary {
prop_assert!(
(0.0..=1.0).contains(&s.blocked_fraction),
"blocked_fraction out of range: {}",
s.blocked_fraction
);
}
}
#[test]
fn blocked_before_oracle_dominant_family_is_scan_wide(
count in 3usize..20usize,
family in prop_oneof![Just(BlockFamily::Authorization), Just(BlockFamily::Method)],
) {
let reason = match family {
BlockFamily::Authorization =>
"auth gate fired before technique (no credential provided)",
BlockFamily::Method => "method-level rejection before resource lookup",
_ => unreachable!(),
};
let findings: Vec<_> = (0..count)
.map(|_| inapplicable_finding(Some(family), reason))
.collect();
let (status, summary) = compute_observability(&findings);
if status == ObservabilityStatus::BlockedBeforeOracleLayer {
let s = summary.expect("block_summary must be Some for BlockedBeforeOracleLayer");
prop_assert!(
matches!(s.dominant_block_family, BlockFamily::Authorization | BlockFamily::Method),
"dominant_block_family must be a scan-wide family"
);
}
}
}