parlov-analysis 0.7.0

Analysis engine trait and signal detection for parlov.
Documentation
//! Unit and property tests for the `passes_not_present_gate` coverage check.
//!
//! No mocks — every test constructs `EvidenceEvent` values via the public
//! `positive`/`contradictory` constructors and exercises the gate directly.

use proptest::prelude::*;

use super::{passes_not_present_gate, MIN_CONTRADICTORY_TECHNIQUES, STRONG_THRESHOLD};
use crate::aggregation::reducer::EvidenceEvent;
use crate::existence::families::SignalFamily;

/// Convenience: build a Contradictory event at the given weight.
fn weak(weight: f64) -> EvidenceEvent {
    EvidenceEvent::contradictory(SignalFamily::General, "weak", weight, weight)
}

/// Convenience: build a Strong-bucket Contradictory event (weight 0.25).
fn strong() -> EvidenceEvent {
    EvidenceEvent::contradictory(SignalFamily::General, "strong", 0.25, 0.25)
}

/// Convenience: build a Positive event with a small log-odds magnitude.
fn positive() -> EvidenceEvent {
    EvidenceEvent::positive(SignalFamily::General, "pos", 0.5, 0.5)
}

// --- explicit unit cases ---------------------------------------------------

#[test]
fn empty_events_fails_gate() {
    assert!(!passes_not_present_gate(&[]));
}

#[test]
fn three_weak_contradictories_fails_no_strong() {
    let events = [weak(0.05), weak(0.05), weak(0.05)];
    assert!(!passes_not_present_gate(&events));
}

#[test]
fn three_contradictories_one_strong_passes() {
    let events = [strong(), weak(0.05), weak(0.05)];
    assert!(passes_not_present_gate(&events));
}

#[test]
fn two_strong_contradictories_fails_below_min_count() {
    let events = [strong(), strong()];
    assert!(!passes_not_present_gate(&events));
}

#[test]
fn positive_disqualifies_even_with_strong_contradictories() {
    let events = [strong(), weak(0.05), weak(0.05), positive()];
    assert!(!passes_not_present_gate(&events));
}

#[test]
fn ten_weak_contradictories_no_strong_fails() {
    let events: Vec<EvidenceEvent> = (0..10).map(|_| weak(0.05)).collect();
    assert!(!passes_not_present_gate(&events));
}

#[test]
fn weight_exactly_at_strong_threshold_counts_as_strong() {
    let events = [weak(STRONG_THRESHOLD), weak(0.05), weak(0.05)];
    assert!(passes_not_present_gate(&events));
}

#[test]
fn weight_just_below_strong_threshold_does_not_count() {
    let just_below = STRONG_THRESHOLD - 0.000_01;
    let events = [weak(just_below), weak(just_below), weak(just_below)];
    assert!(!passes_not_present_gate(&events));
}

// --- property tests --------------------------------------------------------

proptest! {
    /// Any positive event sinks the gate, regardless of how many strong contradictories
    /// fired alongside.
    #[test]
    fn any_positive_event_fails_the_gate(
        positives in 1usize..=5,
        contradictories in 0usize..=10,
    ) {
        let mut events: Vec<EvidenceEvent> =
            (0..contradictories).map(|_| strong()).collect();
        events.extend((0..positives).map(|_| positive()));
        prop_assert!(!passes_not_present_gate(&events));
    }

    /// Below the minimum contradictory count, the gate cannot pass — even at
    /// Strong weights and no positives.
    #[test]
    fn fewer_than_min_contradictories_fails_the_gate(
        contradictories in 0usize..MIN_CONTRADICTORY_TECHNIQUES,
    ) {
        let events: Vec<EvidenceEvent> = (0..contradictories).map(|_| strong()).collect();
        prop_assert!(!passes_not_present_gate(&events));
    }

    /// Enough Strong contradictories with no positives passes the gate, even with
    /// weak contradictories piled on top.
    #[test]
    fn enough_strong_contradictories_passes(extra_weak in 0usize..=10) {
        let mut events: Vec<EvidenceEvent> =
            (0..MIN_CONTRADICTORY_TECHNIQUES).map(|_| strong()).collect();
        events.extend((0..extra_weak).map(|_| weak(0.05)));
        prop_assert!(passes_not_present_gate(&events));
    }

    /// Without any Strong contradictory, the gate fails no matter how many weak
    /// contradictories fire.
    #[test]
    fn no_strong_techniques_fails_regardless_of_count(
        n_weak in MIN_CONTRADICTORY_TECHNIQUES..=20,
    ) {
        let events: Vec<EvidenceEvent> = (0..n_weak).map(|_| weak(0.05)).collect();
        prop_assert!(!passes_not_present_gate(&events));
    }
}