parlov-analysis 0.7.0

Analysis engine trait and signal detection for parlov.
Documentation
use super::*;

/// Same-status pair short-circuits to `NotPresent` immediately.
#[test]
fn evaluate_complete_not_present_on_same_status() {
    let ds = diff_set_1(404, 404);
    let SampleDecision::Complete(result, _outcome) = ExistenceAnalyzer.evaluate(&ds) else {
        panic!("expected Complete");
    };
    assert_eq!(result.verdict, OracleVerdict::NotPresent);
    assert_eq!(result.severity, None);
}

/// Same-status short-circuit on a `StatusCodeDiff` technique produces `Contradictory`.
#[test]
fn same_status_on_status_code_diff_produces_contradictory() {
    // StatusCodeDiff with same status: server normalises → Contradictory with weight > 0.
    let ds = diff_set_1(404, 404);
    let SampleDecision::Complete(_result, outcome) = ExistenceAnalyzer.evaluate(&ds) else {
        panic!("expected Complete");
    };
    let StrategyOutcome::Contradictory(_inner, weight) = outcome else {
        panic!("expected Contradictory outcome for same-status StatusCodeDiff");
    };
    assert!(weight > 0.0, "Contradictory weight must be > 0.0");
}

/// Same-status short-circuit on a `RedirectDiff` technique produces `NoSignal`.
#[test]
fn same_status_on_redirect_diff_produces_no_signal() {
    use parlov_core::{
        always_applicable, NormativeStrength, OracleClass, SignalSurface, Technique, Vector,
    };
    let ds = DifferentialSet {
        baseline: vec![fake_exchange(404)],
        probe: vec![fake_exchange(404)],
        canonical: None,
        technique: Technique {
            id: "test-redirect-diff-same",
            name: "Test redirect diff same",
            oracle_class: OracleClass::Existence,
            vector: Vector::RedirectDiff,
            strength: NormativeStrength::Should,
            normalization_weight: None,
            inverted_signal_weight: None,
            method_relevant: false,
            parser_relevant: false,
            applicability: always_applicable,
            contradiction_surface: SignalSurface::Status,
        },
    };
    let SampleDecision::Complete(_result, outcome) = ExistenceAnalyzer.evaluate(&ds) else {
        panic!("expected Complete");
    };
    assert!(
        matches!(outcome, StrategyOutcome::NoSignal(_)),
        "same-status on RedirectDiff must produce NoSignal outcome"
    );
}

/// `normalization_weight: Some(w)` on `SameStatus` path produces `Contradictory` with that weight.
#[test]
fn same_status_with_normalization_weight_is_contradictory() {
    use parlov_core::{
        always_applicable, NormativeStrength, OracleClass, SignalSurface, Technique, Vector,
    };
    let ds = DifferentialSet {
        baseline: vec![fake_exchange(404)],
        probe: vec![fake_exchange(404)],
        canonical: None,
        technique: Technique {
            id: "test-normalization-weight",
            name: "Test normalization weight",
            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,
        },
    };
    let SampleDecision::Complete(_result, outcome) = ExistenceAnalyzer.evaluate(&ds) else {
        panic!("expected Complete");
    };
    let StrategyOutcome::Contradictory(_inner, weight) = outcome else {
        panic!("expected Contradictory for Some(0.2) normalization_weight on SameStatus");
    };
    assert!(
        (weight - 0.2).abs() < f32::EPSILON,
        "weight must equal normalization_weight; got {weight}"
    );
}

/// `normalization_weight: None` on `SameStatus` path produces `NoSignal`.
#[test]
fn same_status_without_normalization_weight_is_no_signal() {
    use parlov_core::{
        always_applicable, NormativeStrength, OracleClass, SignalSurface, Technique, Vector,
    };
    let ds = DifferentialSet {
        baseline: vec![fake_exchange(404)],
        probe: vec![fake_exchange(404)],
        canonical: None,
        technique: Technique {
            id: "test-no-normalization",
            name: "Test no normalization",
            oracle_class: OracleClass::Existence,
            vector: Vector::CacheProbing,
            strength: NormativeStrength::May,
            normalization_weight: None,
            inverted_signal_weight: None,
            method_relevant: false,
            parser_relevant: false,
            applicability: always_applicable,
            contradiction_surface: SignalSurface::Status,
        },
    };
    let SampleDecision::Complete(_result, outcome) = ExistenceAnalyzer.evaluate(&ds) else {
        panic!("expected Complete");
    };
    assert!(
        matches!(outcome, StrategyOutcome::NoSignal(_)),
        "normalization_weight: None on SameStatus must produce NoSignal"
    );
}

use proptest::prelude::*;

proptest! {
    /// For any technique with `normalization_weight: Some(w)` where `w > 0`, the SameStatus
    /// path always produces `Contradictory(_, w)`.
    #[test]
    fn same_status_normalization_weight_some_always_contradictory(w in 0.01f32..=1.0f32) {
        use parlov_core::{NormativeStrength, OracleClass, SignalSurface, Technique, Vector, always_applicable};
        let ds = DifferentialSet {
            baseline: vec![fake_exchange(404)],
            probe: vec![fake_exchange(404)],
            canonical: None,
            technique: Technique {
                id: "test-prop",
                name: "Test prop",
                oracle_class: OracleClass::Existence,
                vector: Vector::StatusCodeDiff,
                strength: NormativeStrength::Must,
                normalization_weight: Some(w),
                inverted_signal_weight: None,
                method_relevant: false,
                parser_relevant: false,
                applicability: always_applicable,
                contradiction_surface: SignalSurface::Status,
            },
        };
        let SampleDecision::Complete(_result, outcome) = ExistenceAnalyzer.evaluate(&ds) else {
            panic!("expected Complete");
        };
        let StrategyOutcome::Contradictory(_, weight) = outcome else {
            return Err(proptest::test_runner::TestCaseError::fail("expected Contradictory"));
        };
        prop_assert!(
            (weight - w).abs() < f32::EPSILON,
            "weight {weight} must equal normalization_weight {w}"
        );
    }

    /// For any technique with `normalization_weight: None`, the SameStatus path always produces
    /// `NoSignal`.
    #[test]
    fn same_status_normalization_weight_none_always_no_signal(status in 200u16..=599u16) {
        use parlov_core::{NormativeStrength, OracleClass, SignalSurface, Technique, Vector, always_applicable};
        let valid_status = if http::StatusCode::from_u16(status).is_err() {
            404u16
        } else {
            status
        };
        let ds = DifferentialSet {
            baseline: vec![fake_exchange(valid_status)],
            probe: vec![fake_exchange(valid_status)],
            canonical: None,
            technique: Technique {
                id: "test-none-prop",
                name: "Test none prop",
                oracle_class: OracleClass::Existence,
                vector: Vector::CacheProbing,
                strength: NormativeStrength::May,
                normalization_weight: None,
                inverted_signal_weight: None,
                method_relevant: false,
                parser_relevant: false,
                applicability: always_applicable,
                contradiction_surface: SignalSurface::Status,
            },
        };
        let SampleDecision::Complete(_result, outcome) = ExistenceAnalyzer.evaluate(&ds) else {
            return Err(proptest::test_runner::TestCaseError::fail("expected Complete"));
        };
        prop_assert!(
            matches!(outcome, StrategyOutcome::NoSignal(_)),
            "normalization_weight: None on SameStatus must always produce NoSignal"
        );
    }
}