parlov-elicit 0.5.0

Elicitation engine: strategy selection and probe plan generation for parlov.
Documentation
use super::*;
use http::{HeaderMap, Method};
use parlov_core::{always_applicable, NormativeStrength, OracleClass, SignalSurface, Vector};

fn make_probe_def(url: &str, method: Method) -> ProbeDefinition {
    ProbeDefinition {
        url: url.to_owned(),
        method,
        headers: HeaderMap::new(),
        body: None,
    }
}

fn make_metadata() -> StrategyMetadata {
    StrategyMetadata {
        strategy_id: "test-strategy",
        strategy_name: "Test Strategy",
        risk: RiskLevel::Safe,
    }
}

fn make_technique() -> Technique {
    Technique {
        id: "test",
        name: "Test technique",
        oracle_class: OracleClass::Existence,
        vector: Vector::StatusCodeDiff,
        strength: NormativeStrength::Should,
        normalization_weight: Some(0.2),
        inverted_signal_weight: None,
        method_relevant: false,
        parser_relevant: false,
        applicability: always_applicable,
        contradiction_surface: SignalSurface::Status,
    }
}

// --- RiskLevel ordering ---

#[test]
fn risk_level_safe_less_than_method_destructive() {
    assert!(RiskLevel::Safe < RiskLevel::MethodDestructive);
}

#[test]
fn risk_level_method_destructive_less_than_operation_destructive() {
    assert!(RiskLevel::MethodDestructive < RiskLevel::OperationDestructive);
}

#[test]
fn risk_level_safe_less_than_operation_destructive() {
    assert!(RiskLevel::Safe < RiskLevel::OperationDestructive);
}

// --- RiskLevel serialization round-trip ---

#[test]
fn risk_level_method_destructive_roundtrip() {
    let original = RiskLevel::MethodDestructive;
    let json = serde_json::to_string(&original).expect("serialize failed");
    let deserialized: RiskLevel = serde_json::from_str(&json).expect("deserialize failed");
    assert_eq!(deserialized, original);
}

// --- Type construction ---

#[test]
fn strategy_metadata_fields_accessible() {
    let m = make_metadata();
    assert_eq!(m.strategy_id, "test-strategy");
    assert_eq!(m.strategy_name, "Test Strategy");
    assert_eq!(m.risk, RiskLevel::Safe);
}

#[test]
fn probe_pair_fields_accessible() {
    let pair = ProbePair {
        baseline: make_probe_def("https://example.com/1", Method::GET),
        probe: make_probe_def("https://example.com/999", Method::GET),
        canonical_baseline: None,
        metadata: make_metadata(),
        technique: make_technique(),
        chain_provenance: None,
    };
    assert_eq!(pair.baseline.url, "https://example.com/1");
    assert_eq!(pair.probe.url, "https://example.com/999");
    assert_eq!(pair.metadata.risk, RiskLevel::Safe);
}

#[test]
fn burst_spec_fields_accessible() {
    let burst = BurstSpec {
        baseline: make_probe_def("https://example.com/1", Method::GET),
        probe: make_probe_def("https://example.com/999", Method::GET),
        burst_count: 30,
        metadata: make_metadata(),
        technique: make_technique(),
        chain_provenance: None,
    };
    assert_eq!(burst.burst_count, 30);
}

#[test]
fn probe_spec_pair_variant_accessible() {
    let spec = ProbeSpec::Pair(ProbePair {
        baseline: make_probe_def("https://example.com/1", Method::GET),
        probe: make_probe_def("https://example.com/999", Method::GET),
        canonical_baseline: None,
        metadata: make_metadata(),
        technique: make_technique(),
        chain_provenance: None,
    });
    assert!(matches!(spec, ProbeSpec::Pair(_)));
}

#[test]
fn probe_spec_burst_variant_accessible() {
    let spec = ProbeSpec::Burst(BurstSpec {
        baseline: make_probe_def("https://example.com/1", Method::GET),
        probe: make_probe_def("https://example.com/999", Method::GET),
        burst_count: 30,
        metadata: make_metadata(),
        technique: make_technique(),
        chain_provenance: None,
    });
    assert!(matches!(spec, ProbeSpec::Burst(_)));
}

#[test]
fn probe_spec_header_diff_variant_accessible() {
    let spec = ProbeSpec::HeaderDiff(ProbePair {
        baseline: make_probe_def("https://example.com/1", Method::GET),
        probe: make_probe_def("https://example.com/999", Method::GET),
        canonical_baseline: None,
        metadata: make_metadata(),
        technique: make_technique(),
        chain_provenance: None,
    });
    assert!(matches!(spec, ProbeSpec::HeaderDiff(_)));
}

#[test]
fn probe_spec_technique_accessor_returns_correct_technique() {
    let spec = ProbeSpec::Pair(ProbePair {
        baseline: make_probe_def("https://example.com/1", Method::GET),
        probe: make_probe_def("https://example.com/999", Method::GET),
        canonical_baseline: None,
        metadata: make_metadata(),
        technique: make_technique(),
        chain_provenance: None,
    });
    assert_eq!(spec.technique().id, "test");
    assert_eq!(spec.technique().vector, Vector::StatusCodeDiff);
}