parlov-analysis 0.5.0

Analysis engine trait and signal detection for parlov.
Documentation
//! Unit tests for `ExistenceAnalyzer` — stability, short-circuit, scoring.

use super::*;
use parlov_core::OracleVerdict;

use crate::signals::tests::{diff_set_with_statuses, fake_exchange, status_code_diff_technique};

fn diff_set_1(baseline_status: u16, probe_status: u16) -> DifferentialSet {
    DifferentialSet {
        baseline: vec![fake_exchange(baseline_status)],
        probe: vec![fake_exchange(probe_status)],
        technique: status_code_diff_technique(),
    }
}

fn diff_set_n(baseline_statuses: &[u16], probe_statuses: &[u16]) -> DifferentialSet {
    diff_set_with_statuses(baseline_statuses, probe_statuses)
}

// --- Stability / NeedMore tests ---

#[test]
fn evaluate_1_sample_diff_returns_need_more() {
    let ds = diff_set_1(403, 404);
    assert!(matches!(
        ExistenceAnalyzer.evaluate(&ds),
        SampleDecision::NeedMore
    ));
}

#[test]
fn evaluate_2_samples_diff_returns_need_more() {
    let ds = diff_set_n(&[403, 403], &[404, 404]);
    assert!(matches!(
        ExistenceAnalyzer.evaluate(&ds),
        SampleDecision::NeedMore
    ));
}

#[test]
fn evaluate_3_samples_stable_diff_confirmed() {
    let ds = diff_set_n(&[403, 403, 403], &[404, 404, 404]);
    let SampleDecision::Complete(result) = ExistenceAnalyzer.evaluate(&ds) else {
        panic!("expected Complete");
    };
    assert_eq!(result.verdict, OracleVerdict::Confirmed);
    assert!(result.severity.is_some());
}

#[test]
fn evaluate_3_samples_unstable_probe_not_present() {
    let ds = diff_set_n(&[403, 403, 403], &[404, 200, 404]);
    let SampleDecision::Complete(result) = ExistenceAnalyzer.evaluate(&ds) else {
        panic!("expected Complete");
    };
    assert_eq!(result.verdict, OracleVerdict::NotPresent);
    assert_eq!(result.severity, None);
}

#[test]
fn evaluate_3_samples_unstable_baseline_not_present() {
    let ds = diff_set_n(&[403, 200, 403], &[404, 404, 404]);
    let SampleDecision::Complete(result) = ExistenceAnalyzer.evaluate(&ds) else {
        panic!("expected Complete");
    };
    assert_eq!(result.verdict, OracleVerdict::NotPresent);
    assert_eq!(result.severity, None);
}

// --- Same-status short-circuit ---

#[test]
fn evaluate_complete_not_present_on_same_status() {
    let ds = diff_set_1(404, 404);
    let SampleDecision::Complete(result) = ExistenceAnalyzer.evaluate(&ds) else {
        panic!("expected Complete");
    };
    assert_eq!(result.verdict, OracleVerdict::NotPresent);
    assert_eq!(result.severity, None);
}

#[test]
fn evaluate_complete_confirmed_on_403_vs_404() {
    let ds = diff_set_n(&[403, 403, 403], &[404, 404, 404]);
    let SampleDecision::Complete(result) = ExistenceAnalyzer.evaluate(&ds) else {
        panic!("expected Complete");
    };
    assert_eq!(result.verdict, OracleVerdict::Confirmed);
    assert!(result.severity.is_some());
}

#[test]
fn analyze_provided_method_delegates_to_evaluate() {
    let ds = diff_set_n(&[200, 200, 200], &[404, 404, 404]);
    let result = ExistenceAnalyzer.analyze(&ds);
    assert_eq!(result.verdict, OracleVerdict::Confirmed);
    assert!(result.severity.is_some());
}

#[test]
fn need_more_variant_is_constructible() {
    assert!(matches!(SampleDecision::NeedMore, SampleDecision::NeedMore));
}

// --- Technique context propagation ---

#[test]
fn result_carries_technique_metadata() {
    let ds = diff_set_n(&[403, 403, 403], &[404, 404, 404]);
    let SampleDecision::Complete(result) = ExistenceAnalyzer.evaluate(&ds) else {
        panic!("expected Complete");
    };
    assert!(result.technique_id.is_some());
    assert!(result.vector.is_some());
    assert!(result.normative_strength.is_some());
}

// --- Scoring fields populated ---

#[test]
fn result_carries_confidence_and_impact() {
    let ds = diff_set_n(&[403, 403, 403], &[404, 404, 404]);
    let SampleDecision::Complete(result) = ExistenceAnalyzer.evaluate(&ds) else {
        panic!("expected Complete");
    };
    assert!(result.confidence > 0);
    assert!(result.impact_class.is_some());
    assert!(!result.reasons.is_empty());
}

// --- Signal extraction ---

#[test]
fn result_carries_status_code_diff_signal() {
    let ds = diff_set_n(&[403, 403, 403], &[404, 404, 404]);
    let SampleDecision::Complete(result) = ExistenceAnalyzer.evaluate(&ds) else {
        panic!("expected Complete");
    };
    assert!(
        result
            .signals
            .iter()
            .any(|s| s.kind == parlov_core::SignalKind::StatusCodeDiff),
        "expected StatusCodeDiff signal"
    );
}

#[test]
fn unstable_result_carries_signal() {
    let ds = diff_set_n(&[403, 403, 403], &[404, 200, 404]);
    let SampleDecision::Complete(result) = ExistenceAnalyzer.evaluate(&ds) else {
        panic!("expected Complete");
    };
    assert_eq!(result.verdict, OracleVerdict::NotPresent);
    assert!(
        result.primary_evidence().contains("unstable"),
        "unstable result should carry unstable signal"
    );
}