use bytes::Bytes;
use http::{HeaderMap, StatusCode};
use parlov_core::{
always_applicable, DifferentialSet, NormativeStrength, OracleClass, ProbeDefinition,
ProbeExchange, ResponseSurface, SignalSurface, Technique, Vector,
};
use proptest::prelude::*;
use super::{compute_modifiers, EvidenceModifiers};
fn technique() -> Technique {
Technique {
id: "test-modifiers",
name: "Test modifiers",
oracle_class: OracleClass::Existence,
vector: Vector::StatusCodeDiff,
strength: NormativeStrength::Must,
normalization_weight: Some(0.2),
inverted_signal_weight: None,
method_relevant: false,
parser_relevant: false,
applicability: always_applicable,
contradiction_surface: SignalSurface::Status,
}
}
fn make_exchange(status: u16) -> ProbeExchange {
ProbeExchange {
request: ProbeDefinition {
url: "https://example.com/r/1".into(),
method: http::Method::GET,
headers: HeaderMap::new(),
body: None,
},
response: ResponseSurface {
status: StatusCode::from_u16(status).expect("valid status"),
headers: HeaderMap::new(),
body: Bytes::new(),
timing_ns: 0,
},
}
}
fn diff_set_uniform_401() -> DifferentialSet {
DifferentialSet {
baseline: vec![make_exchange(401)],
probe: vec![make_exchange(401)],
canonical: None,
technique: technique(),
}
}
fn diff_set_distinct_status() -> DifferentialSet {
DifferentialSet {
baseline: vec![make_exchange(200)],
probe: vec![make_exchange(404)],
canonical: None,
technique: technique(),
}
}
#[test]
fn default_returns_all_ones() {
let m = EvidenceModifiers::default();
assert!((m.surface_relevance - 1.0).abs() < f64::EPSILON);
assert!((m.precondition_confidence - 1.0).abs() < f64::EPSILON);
assert!((m.control_integrity - 1.0).abs() < f64::EPSILON);
}
#[test]
fn default_total_is_one() {
assert!((EvidenceModifiers::default().total() - 1.0).abs() < f64::EPSILON);
}
#[test]
fn default_is_not_blocked() {
assert!(!EvidenceModifiers::default().is_blocked());
}
#[test]
fn total_is_multiplicative_product() {
let m = EvidenceModifiers {
surface_relevance: 0.5,
precondition_confidence: 0.5,
control_integrity: 0.5,
};
assert!((m.total() - 0.125).abs() < f64::EPSILON);
}
#[test]
fn total_with_one_zero_is_zero() {
let m = EvidenceModifiers {
surface_relevance: 0.0,
precondition_confidence: 1.0,
control_integrity: 1.0,
};
assert!(m.total().abs() < f64::EPSILON);
}
#[test]
fn is_blocked_true_when_surface_relevance_zero() {
let m = EvidenceModifiers {
surface_relevance: 0.0,
precondition_confidence: 1.0,
control_integrity: 1.0,
};
assert!(m.is_blocked());
}
#[test]
fn is_blocked_true_when_precondition_confidence_zero() {
let m = EvidenceModifiers {
surface_relevance: 1.0,
precondition_confidence: 0.0,
control_integrity: 1.0,
};
assert!(m.is_blocked());
}
#[test]
fn is_blocked_true_when_control_integrity_zero() {
let m = EvidenceModifiers {
surface_relevance: 1.0,
precondition_confidence: 1.0,
control_integrity: 0.0,
};
assert!(m.is_blocked());
}
#[test]
fn is_blocked_true_when_all_zero() {
let m = EvidenceModifiers {
surface_relevance: 0.0,
precondition_confidence: 0.0,
control_integrity: 0.0,
};
assert!(m.is_blocked());
}
#[test]
fn is_blocked_false_when_all_positive() {
let m = EvidenceModifiers {
surface_relevance: 0.01,
precondition_confidence: 0.01,
control_integrity: 0.01,
};
assert!(!m.is_blocked());
}
#[test]
fn compute_modifiers_blocks_uniform_401_no_auth() {
let ds = diff_set_uniform_401();
let mr = compute_modifiers(&technique(), &ds);
assert!(
mr.is_blocked(),
"uniform 401 must zero precondition_confidence"
);
assert!(mr.block_reason.is_some(), "block reason must be populated");
}
#[test]
fn compute_modifiers_returns_default_on_distinct_status() {
let ds = diff_set_distinct_status();
let mr = compute_modifiers(&technique(), &ds);
assert_eq!(mr.modifiers, EvidenceModifiers::default());
assert!(mr.block_reason.is_none());
}
#[test]
fn compute_modifiers_total_is_one_on_distinct_status() {
let ds = diff_set_distinct_status();
let mr = compute_modifiers(&technique(), &ds);
assert!((mr.modifiers.total() - 1.0).abs() < f64::EPSILON);
}
fn make_exchange_with(status: u16, body: Bytes) -> ProbeExchange {
ProbeExchange {
request: ProbeDefinition {
url: "https://example.com/r/1".into(),
method: http::Method::GET,
headers: HeaderMap::new(),
body: None,
},
response: ResponseSurface {
status: StatusCode::from_u16(status).expect("valid status"),
headers: HeaderMap::new(),
body,
timing_ns: 0,
},
}
}
#[test]
fn compute_modifiers_blocks_uniform_status_with_diverging_body() {
let t = technique();
let ds = DifferentialSet {
baseline: vec![make_exchange_with(200, Bytes::from(vec![b'x'; 1000]))],
probe: vec![make_exchange_with(200, Bytes::from_static(b"x"))],
canonical: None,
technique: t,
};
let mr = compute_modifiers(&t, &ds);
assert!(
mr.is_blocked(),
"diverging body on Status-surface technique must zero surface_relevance"
);
assert_eq!(
mr.block_reason,
Some(super::PreconditionBlock::SurfaceMismatch),
"block reason must be SurfaceMismatch"
);
assert!((mr.modifiers.surface_relevance).abs() < f64::EPSILON);
assert!((mr.modifiers.precondition_confidence - 1.0).abs() < f64::EPSILON);
}
#[test]
fn compute_modifiers_precondition_takes_priority_over_surface() {
let t = technique();
let ds = DifferentialSet {
baseline: vec![make_exchange_with(405, Bytes::from(vec![b'x'; 1000]))],
probe: vec![make_exchange_with(405, Bytes::from_static(b"x"))],
canonical: None,
technique: t,
};
let mr = compute_modifiers(&t, &ds);
assert!(mr.is_blocked());
assert_eq!(
mr.block_reason,
Some(super::PreconditionBlock::MethodGateBeforeResource),
"method-gate (precondition) must be reported first when both gates fire"
);
}
#[test]
fn compute_modifiers_inert_when_body_surface_with_diverging_body() {
let mut t = technique();
t.contradiction_surface = SignalSurface::Body;
let ds = DifferentialSet {
baseline: vec![make_exchange_with(200, Bytes::from(vec![b'x'; 1000]))],
probe: vec![make_exchange_with(200, Bytes::from_static(b"x"))],
canonical: None,
technique: t,
};
let mr = compute_modifiers(&t, &ds);
assert!(
!mr.is_blocked(),
"Body-surface technique must not block on body diff"
);
assert_eq!(mr.modifiers, EvidenceModifiers::default());
}
#[test]
fn compute_modifiers_blocks_when_canonical_succeeds_mutated_fails() {
let t = technique();
let ds = DifferentialSet {
baseline: vec![make_exchange(404)],
probe: vec![make_exchange(404)],
canonical: Some(make_exchange(200)),
technique: t,
};
let mr = compute_modifiers(&t, &ds);
assert!(mr.is_blocked());
assert_eq!(
mr.block_reason,
Some(super::PreconditionBlock::MutationDestroyedControl),
"block reason must be MutationDestroyedControl when canonical 2xx + mutated non-2xx"
);
assert!((mr.modifiers.control_integrity).abs() < f64::EPSILON);
assert!((mr.modifiers.precondition_confidence - 1.0).abs() < f64::EPSILON);
assert!((mr.modifiers.surface_relevance - 1.0).abs() < f64::EPSILON);
}
#[test]
fn compute_modifiers_blocks_when_canonical_returns_301() {
let t = technique();
let ds = DifferentialSet {
baseline: vec![make_exchange(200)],
probe: vec![make_exchange(200)],
canonical: Some(make_exchange(301)),
technique: t,
};
let mr = compute_modifiers(&t, &ds);
assert!(mr.is_blocked());
assert_eq!(
mr.block_reason,
Some(super::PreconditionBlock::MutationDestroyedControl),
);
}
#[test]
fn compute_modifiers_inert_when_no_canonical() {
let t = technique();
let ds = diff_set_distinct_status();
let mr = compute_modifiers(&t, &ds);
assert_eq!(mr.modifiers, EvidenceModifiers::default());
assert_eq!(mr.block_reason, None);
}
#[test]
fn compute_modifiers_precondition_takes_priority_over_control() {
let t = technique();
let ds = DifferentialSet {
baseline: vec![make_exchange(401)],
probe: vec![make_exchange(401)],
canonical: Some(make_exchange(200)),
technique: t,
};
let mr = compute_modifiers(&t, &ds);
assert!(mr.is_blocked());
assert_eq!(
mr.block_reason,
Some(
crate::aggregation::PreconditionBlock::AuthGateBeforeTechnique {
credential_state: crate::aggregation::auth_types::CredentialBlockKind::NoCredential,
layer: crate::aggregation::AuthBlockLayer::Origin,
}
),
"auth-gate (precondition) must be reported first when both gates fire"
);
}
#[test]
fn compute_modifiers_control_takes_priority_over_surface() {
let t = technique();
let ds = DifferentialSet {
baseline: vec![make_exchange_with(404, Bytes::from(vec![b'x'; 1000]))],
probe: vec![make_exchange_with(404, Bytes::from_static(b"x"))],
canonical: Some(make_exchange(200)),
technique: t,
};
let mr = compute_modifiers(&t, &ds);
assert!(mr.is_blocked());
assert_eq!(
mr.block_reason,
Some(super::PreconditionBlock::MutationDestroyedControl),
"control must be reported before surface when both gates fire"
);
}
proptest! {
#[test]
fn total_bounded_when_fields_bounded(
sr in 0.0f64..=1.0,
pc in 0.0f64..=1.0,
ci in 0.0f64..=1.0,
) {
let m = EvidenceModifiers {
surface_relevance: sr,
precondition_confidence: pc,
control_integrity: ci,
};
prop_assert!(m.total() >= 0.0);
prop_assert!(m.total() <= 1.0);
}
#[test]
fn is_blocked_iff_any_field_zero(
sr in 0.0f64..=1.0,
pc in 0.0f64..=1.0,
ci in 0.0f64..=1.0,
) {
let m = EvidenceModifiers {
surface_relevance: sr,
precondition_confidence: pc,
control_integrity: ci,
};
let any_zero = sr == 0.0 || pc == 0.0 || ci == 0.0;
prop_assert_eq!(m.is_blocked(), any_zero);
}
#[test]
fn total_commutative_under_permutation(
a in 0.0f64..=1.0,
b in 0.0f64..=1.0,
c in 0.0f64..=1.0,
) {
let m1 = EvidenceModifiers {
surface_relevance: a,
precondition_confidence: b,
control_integrity: c,
};
let m2 = EvidenceModifiers {
surface_relevance: c,
precondition_confidence: a,
control_integrity: b,
};
let m3 = EvidenceModifiers {
surface_relevance: b,
precondition_confidence: c,
control_integrity: a,
};
prop_assert!((m1.total() - m2.total()).abs() < f64::EPSILON);
prop_assert!((m1.total() - m3.total()).abs() < f64::EPSILON);
}
#[test]
fn compute_modifiers_referentially_transparent(b_status in 200u16..=599, p_status in 200u16..=599) {
let b_status = if http::StatusCode::from_u16(b_status).is_err() { 404 } else { b_status };
let p_status = if http::StatusCode::from_u16(p_status).is_err() { 404 } else { p_status };
let ds = DifferentialSet {
baseline: vec![make_exchange(b_status)],
probe: vec![make_exchange(p_status)],
canonical: None,
technique: technique(),
};
let mr1 = compute_modifiers(&technique(), &ds);
let mr2 = compute_modifiers(&technique(), &ds);
prop_assert_eq!(mr1, mr2);
}
}