use super::*;
use parlov_core::{ImpactClass, NormativeStrength, OracleVerdict, Severity, Signal, SignalKind};
fn pattern(base_confidence: u8, base_impact: u8) -> PatternMatch {
PatternMatch {
base_confidence,
base_impact,
label: Some("Test pattern"),
leaks: Some("Test leak"),
rfc_basis: Some("RFC 9110"),
}
}
#[test]
fn zero_base_returns_not_present() {
let p = PatternMatch {
base_confidence: 0,
base_impact: 0,
label: None,
leaks: None,
rfc_basis: None,
};
let out = compute(&p, &[], NormativeStrength::Must, 404);
assert_eq!(out.verdict, OracleVerdict::NotPresent);
assert_eq!(out.confidence, 0);
assert!(out.severity.is_none());
}
#[test]
fn high_base_confidence_yields_confirmed() {
let out = compute(&pattern(90, 50), &[], NormativeStrength::Must, 200);
assert_eq!(out.verdict, OracleVerdict::Confirmed);
assert_eq!(out.confidence, 90);
}
#[test]
fn moderate_base_confidence_yields_likely() {
let out = compute(&pattern(70, 35), &[], NormativeStrength::Must, 403);
assert_eq!(out.verdict, OracleVerdict::Likely);
assert_eq!(out.confidence, 70);
}
#[test]
fn low_base_confidence_yields_not_present() {
let out = compute(&pattern(40, 15), &[], NormativeStrength::Must, 418);
assert_eq!(out.verdict, OracleVerdict::NotPresent);
assert_eq!(out.confidence, 40);
}
#[test]
fn confirmed_verdict_gets_full_severity() {
let out = compute(&pattern(90, 50), &[], NormativeStrength::Must, 200);
assert_eq!(out.severity, Some(Severity::Medium));
}
#[test]
fn likely_verdict_caps_severity_one_below() {
let out = compute(&pattern(70, 50), &[], NormativeStrength::Must, 200);
assert_eq!(out.severity, Some(Severity::Low));
}
#[test]
fn signals_add_to_confidence() {
let signals = vec![Signal {
kind: SignalKind::HeaderPresence,
evidence: "etag present in baseline, absent in probe".into(),
rfc_basis: None,
}];
let out = compute(&pattern(75, 35), &signals, NormativeStrength::Must, 403);
assert!(out.confidence >= 80);
assert_eq!(out.verdict, OracleVerdict::Confirmed);
}
#[test]
fn confidence_clamped_to_100() {
let signals = vec![
Signal {
kind: SignalKind::HeaderPresence,
evidence: "etag present in baseline".into(),
rfc_basis: None,
},
Signal {
kind: SignalKind::HeaderPresence,
evidence: "content-range present in baseline".into(),
rfc_basis: None,
},
Signal {
kind: SignalKind::MetadataLeak,
evidence: "Content-Range leaks total resource size: 500 bytes".into(),
rfc_basis: None,
},
];
let out = compute(&pattern(90, 55), &signals, NormativeStrength::Must, 206);
assert!(out.confidence <= 100);
}
#[test]
fn content_range_size_leak_yields_high_impact() {
let signals = vec![Signal {
kind: SignalKind::MetadataLeak,
evidence: "Content-Range leaks total resource size: 1024 bytes".into(),
rfc_basis: None,
}];
let out = compute(&pattern(85, 55), &signals, NormativeStrength::Must, 206);
assert_eq!(out.impact_class, Some(ImpactClass::High));
}
#[test]
fn verdict_from_confidence_thresholds() {
assert_eq!(verdict_from_confidence(100), OracleVerdict::Confirmed);
assert_eq!(verdict_from_confidence(80), OracleVerdict::Confirmed);
assert_eq!(verdict_from_confidence(79), OracleVerdict::Likely);
assert_eq!(verdict_from_confidence(60), OracleVerdict::Likely);
assert_eq!(verdict_from_confidence(59), OracleVerdict::NotPresent);
assert_eq!(verdict_from_confidence(0), OracleVerdict::NotPresent);
}
fn zero_pattern() -> PatternMatch {
PatternMatch {
base_confidence: 0,
base_impact: 0,
label: None,
leaks: None,
rfc_basis: None,
}
}
fn body_content_signal() -> Signal {
Signal {
kind: SignalKind::BodyDiff,
evidence: "body length: 27 (baseline) vs 45 (probe)".into(),
rfc_basis: None,
}
}
fn body_content_type_signal() -> Signal {
Signal {
kind: SignalKind::BodyDiff,
evidence: "content-type: application/json (baseline) vs text/html (probe)".into(),
rfc_basis: None,
}
}
#[test]
fn same_status_no_signals_still_not_present() {
let out = compute(&zero_pattern(), &[], NormativeStrength::Must, 403);
assert_eq!(out.verdict, OracleVerdict::NotPresent);
assert_eq!(out.confidence, 0);
assert!(out.severity.is_none());
}
#[test]
fn same_status_body_diff_may_alone_below_likely() {
let signals = vec![body_content_signal()];
let out = compute(&zero_pattern(), &signals, NormativeStrength::May, 403);
assert_eq!(out.confidence, 53);
assert_eq!(out.verdict, OracleVerdict::NotPresent);
}
#[test]
fn same_status_body_diff_should_reaches_likely() {
let signals = vec![body_content_signal()];
let out = compute(&zero_pattern(), &signals, NormativeStrength::Should, 403);
assert_eq!(out.confidence, 63);
assert_eq!(out.verdict, OracleVerdict::Likely);
assert!(out.severity.is_some());
}
#[test]
fn same_status_body_diff_plus_content_type_may_reaches_likely() {
let signals = vec![body_content_signal(), body_content_type_signal()];
let out = compute(&zero_pattern(), &signals, NormativeStrength::May, 403);
assert_eq!(out.confidence, 62);
assert_eq!(out.verdict, OracleVerdict::Likely);
}
#[test]
fn status_diff_plus_body_diff_attenuated() {
let signals = vec![body_content_signal()];
let out = compute(&pattern(65, 25), &signals, NormativeStrength::May, 400);
assert_eq!(out.confidence, 78);
assert_eq!(out.verdict, OracleVerdict::Likely);
}
#[test]
fn may_strength_confirmed_pattern_caps_at_likely() {
let out = compute(&pattern(92, 50), &[], NormativeStrength::May, 200);
assert_eq!(out.verdict, OracleVerdict::Likely);
assert!(out.confidence >= 80);
}
#[test]
fn should_strength_confirmed_pattern_reaches_confirmed() {
let out = compute(&pattern(92, 50), &[], NormativeStrength::Should, 200);
assert_eq!(out.verdict, OracleVerdict::Confirmed);
}
#[test]
fn must_strength_confirmed_pattern_reaches_confirmed() {
let out = compute(&pattern(92, 50), &[], NormativeStrength::Must, 200);
assert_eq!(out.verdict, OracleVerdict::Confirmed);
}
#[test]
fn may_strength_likely_pattern_stays_likely() {
let out = compute(&pattern(70, 25), &[], NormativeStrength::May, 200);
assert_eq!(out.verdict, OracleVerdict::Likely);
}
#[test]
fn may_strength_not_present_pattern_stays_not_present() {
let out = compute(
&PatternMatch {
base_confidence: 0,
base_impact: 0,
label: None,
leaks: None,
rfc_basis: None,
},
&[],
NormativeStrength::May,
200,
);
assert_eq!(out.verdict, OracleVerdict::NotPresent);
}