parlov-analysis 0.7.0

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

fn redirect_diff_technique() -> parlov_core::Technique {
    use parlov_core::{
        always_applicable, NormativeStrength, OracleClass, SignalSurface, Technique, Vector,
    };
    Technique {
        id: "test-redirect-diff",
        name: "Test redirect diff",
        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,
    }
}

fn redirect_diff_set_n(baseline_statuses: &[u16], probe_statuses: &[u16]) -> DifferentialSet {
    use crate::signals::tests::fake_exchange;
    DifferentialSet {
        baseline: baseline_statuses
            .iter()
            .map(|&s| fake_exchange(s))
            .collect(),
        probe: probe_statuses.iter().map(|&s| fake_exchange(s)).collect(),
        canonical: None,
        technique: redirect_diff_technique(),
    }
}

/// When a `RedirectDiff` probe produces a stable differential but neither side is 3xx,
/// the technique did not fire — `evaluate` must return `NotPresent`, not `Likely`/`Confirmed`.
#[test]
fn redirect_diff_no_3xx_on_either_side_is_not_present() {
    // 200 vs 412: URL manipulation triggered a precondition failure, not redirect behavior.
    let ds = redirect_diff_set_n(&[200, 200, 200], &[412, 412, 412]);
    let SampleDecision::Complete(result, _outcome) = ExistenceAnalyzer.evaluate(&ds) else {
        panic!("expected Complete");
    };
    assert_eq!(
        result.verdict,
        OracleVerdict::NotPresent,
        "stable 200 vs 412 under RedirectDiff must be NotPresent: technique did not fire"
    );
    assert_eq!(result.severity, None);
    assert!(
        result.primary_evidence().contains("did not fire"),
        "signal evidence must explain dismissal; got: {:?}",
        result.primary_evidence()
    );
}

/// When the baseline is 3xx the technique fired — the differential proceeds to normal scoring.
#[test]
fn redirect_diff_baseline_3xx_proceeds_to_scoring() {
    // 301 vs 404: classic redirect-diff existence oracle.
    let ds = redirect_diff_set_n(&[301, 301, 301], &[404, 404, 404]);
    let SampleDecision::Complete(result, _outcome) = ExistenceAnalyzer.evaluate(&ds) else {
        panic!("expected Complete");
    };
    assert_ne!(
        result.verdict,
        OracleVerdict::NotPresent,
        "301 vs 404 under RedirectDiff is a real differential; must not be dismissed"
    );
}

/// When the probe is 3xx the technique fired — the differential proceeds to normal scoring.
#[test]
fn redirect_diff_probe_3xx_proceeds_to_scoring() {
    // 302 vs 404: probe redirected, baseline got not-found.
    // The (FOUND, NOT_FOUND) pattern has base_confidence 80 → Confirmed.
    let ds = redirect_diff_set_n(&[302, 302, 302], &[404, 404, 404]);
    let SampleDecision::Complete(result, _outcome) = ExistenceAnalyzer.evaluate(&ds) else {
        panic!("expected Complete");
    };
    assert_ne!(
        result.verdict,
        OracleVerdict::NotPresent,
        "302 vs 404 under RedirectDiff is a real differential; must not be dismissed"
    );
}

/// The `not_fired_result` path (no relevant differential) produces `StrategyOutcome::NoSignal`.
#[test]
fn not_fired_path_produces_no_signal_outcome() {
    // RedirectDiff with no 3xx on either side → technique did not fire.
    let ds = redirect_diff_set_n(&[200, 200, 200], &[412, 412, 412]);
    let SampleDecision::Complete(_result, outcome) = ExistenceAnalyzer.evaluate(&ds) else {
        panic!("expected Complete");
    };
    assert!(
        matches!(outcome, StrategyOutcome::NoSignal(_)),
        "not-fired path must produce NoSignal outcome"
    );
}