parlov-analysis 0.4.0

Analysis engine trait and signal detection for parlov.
Documentation
//! Unit tests for the scoring pipeline.

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);
}