parlov-analysis 0.6.0

Analysis engine trait and signal detection for parlov.
Documentation
//! Unit tests for per-signal confidence and impact contribution weights.

use super::*;
use parlov_core::Signal;

fn signal(kind: SignalKind, evidence: &str) -> Signal {
    Signal { kind, evidence: evidence.into(), rfc_basis: None }
}

#[test]
fn etag_presence_weight() {
    let s = signal(SignalKind::HeaderPresence, "etag present in baseline");
    let c = weight_signal(&s, NormativeStrength::Must, 1.0);
    assert!((c.confidence - 10.0).abs() < 0.01);
    assert_eq!(c.impact, 5);
    assert_eq!(c.family, SignalFamily::CacheValidator);
}

#[test]
fn content_range_size_leak_weight() {
    let s = signal(
        SignalKind::MetadataLeak,
        "Content-Range leaks total resource size: 1024 bytes",
    );
    let c = weight_signal(&s, NormativeStrength::Must, 1.0);
    assert!((c.confidence - 5.0).abs() < 0.01);
    assert_eq!(c.impact, 15);
    assert_eq!(c.family, SignalFamily::Range);
}

#[test]
fn normative_should_reduces_confidence() {
    let s = signal(SignalKind::HeaderPresence, "etag present in baseline");
    let c = weight_signal(&s, NormativeStrength::Should, 1.0);
    assert!((c.confidence - 9.0).abs() < 0.01);
}

#[test]
fn normative_may_reduces_confidence() {
    let s = signal(SignalKind::HeaderPresence, "etag present in baseline");
    let c = weight_signal(&s, NormativeStrength::May, 1.0);
    assert!((c.confidence - 7.5).abs() < 0.01);
}

#[test]
fn reproducibility_reduces_confidence() {
    let s = signal(SignalKind::HeaderPresence, "etag present in baseline");
    let c = weight_signal(&s, NormativeStrength::Must, 0.7);
    assert!((c.confidence - 7.0).abs() < 0.01);
    // Impact is NOT affected by reproducibility
    assert_eq!(c.impact, 5);
}

#[test]
fn www_authenticate_weight() {
    let s = signal(
        SignalKind::HeaderPresence,
        "www-authenticate present in baseline, absent in probe",
    );
    let c = weight_signal(&s, NormativeStrength::Must, 1.0);
    assert!((c.confidence - 8.0).abs() < 0.01);
    assert_eq!(c.impact, 8);
    assert_eq!(c.family, SignalFamily::Auth);
}

#[test]
fn location_presence_weight() {
    let s = signal(
        SignalKind::HeaderPresence,
        "location present in baseline, absent in probe",
    );
    let c = weight_signal(&s, NormativeStrength::Must, 1.0);
    assert!((c.confidence - 8.0).abs() < 0.01);
    assert_eq!(c.impact, 5);
    assert_eq!(c.family, SignalFamily::Redirect);
}

#[test]
fn location_in_evidence_maps_to_redirect_family() {
    let s = signal(
        SignalKind::HeaderValue,
        "location: \"https://a.com\" (baseline) vs \"https://b.com\" (probe)",
    );
    let c = weight_signal(&s, NormativeStrength::Must, 1.0);
    assert_eq!(c.family, SignalFamily::Redirect);
}

#[test]
fn body_diff_content_weight() {
    let s = signal(
        SignalKind::BodyDiff,
        "body length: 27 (baseline) vs 45 (probe)",
    );
    let c = weight_signal(&s, NormativeStrength::Must, 1.0);
    assert!((c.confidence - 70.0).abs() < 0.01);
    assert_eq!(c.impact, 15);
    assert_eq!(c.family, SignalFamily::ErrorBody);
}

#[test]
fn body_diff_content_type_weight() {
    let s = signal(
        SignalKind::BodyDiff,
        "content-type: application/json (baseline) vs text/html (probe)",
    );
    let c = weight_signal(&s, NormativeStrength::Must, 1.0);
    assert!((c.confidence - 25.0).abs() < 0.01);
    assert_eq!(c.impact, 10);
    assert_eq!(c.family, SignalFamily::ErrorBody);
}

#[test]
fn body_diff_normative_may_reduces_confidence() {
    let s = signal(
        SignalKind::BodyDiff,
        "body content differs (same length: 100 bytes)",
    );
    let c = weight_signal(&s, NormativeStrength::May, 1.0);
    // 70.0 * 0.75 = 52.5
    assert!((c.confidence - 52.5).abs() < 0.01);
}