parlov-elicit 0.5.0

Elicitation engine: strategy selection and probe plan generation for parlov.
Documentation
//! Unit tests for `CpRangeSatisfiable`, `CpRangeSizeProducer`, and `CpRangeSatisfiableConsumer`.

use super::*;
use crate::strategy::Strategy;
use crate::test_utils::minimal_ctx;
use crate::types::{ProbeSpec, RiskLevel};
use http::HeaderValue;
use parlov_core::{NormativeStrength, ResponseClass, Vector};

// --- CpRangeSatisfiable strategy tests ---

#[test]
fn generates_correct_technique_vector() {
    let specs = CpRangeSatisfiable.generate(&minimal_ctx());
    assert_eq!(specs[0].technique().vector, Vector::CacheProbing);
}

#[test]
fn generates_correct_technique_strength() {
    let specs = CpRangeSatisfiable.generate(&minimal_ctx());
    assert_eq!(specs[0].technique().strength, NormativeStrength::Should);
}

#[test]
fn generates_probe_with_correct_header() {
    let specs = CpRangeSatisfiable.generate(&minimal_ctx());
    let ProbeSpec::Pair(pair) = &specs[0] else {
        panic!("expected Pair variant")
    };
    assert_eq!(pair.probe.headers.get("range").unwrap(), "bytes=0-0");
}

#[test]
fn is_always_applicable() {
    assert!(CpRangeSatisfiable.is_applicable(&minimal_ctx()));
}

#[test]
fn risk_is_safe() {
    assert_eq!(CpRangeSatisfiable.risk(), RiskLevel::Safe);
}

// --- CpRangeSizeProducer tests ---

fn headers_with(key: http::header::HeaderName, value: &'static str) -> HeaderMap {
    let mut h = HeaderMap::new();
    h.insert(key, HeaderValue::from_static(value));
    h
}

fn headers_with_two(
    k1: http::header::HeaderName,
    v1: &'static str,
    k2: http::header::HeaderName,
    v2: &'static str,
) -> HeaderMap {
    let mut h = HeaderMap::new();
    h.insert(k1, HeaderValue::from_static(v1));
    h.insert(k2, HeaderValue::from_static(v2));
    h
}

#[test]
fn range_size_producer_admits_partial_content() {
    assert!(CpRangeSizeProducer.admits(ResponseClass::PartialContent));
}

#[test]
fn range_size_producer_admits_range_not_satisfiable() {
    assert!(CpRangeSizeProducer.admits(ResponseClass::RangeNotSatisfiable));
}

#[test]
fn range_size_producer_does_not_admit_success() {
    assert!(!CpRangeSizeProducer.admits(ResponseClass::Success));
}

#[test]
fn range_size_producer_parses_bytes_star_form() {
    let h = headers_with(http::header::CONTENT_RANGE, "bytes */4096");
    let out = CpRangeSizeProducer.extract(ResponseClass::RangeNotSatisfiable, &h);
    assert_eq!(out, Some(ProducerOutput::ContentRangeSize(4096)));
}

#[test]
fn range_size_producer_parses_bytes_range_form() {
    let h = headers_with(http::header::CONTENT_RANGE, "bytes 0-0/512");
    let out = CpRangeSizeProducer.extract(ResponseClass::PartialContent, &h);
    assert_eq!(out, Some(ProducerOutput::ContentRangeSize(512)));
}

#[test]
fn range_size_producer_returns_none_on_size_zero() {
    let h = headers_with(http::header::CONTENT_RANGE, "bytes */0");
    let out = CpRangeSizeProducer.extract(ResponseClass::RangeNotSatisfiable, &h);
    assert_eq!(out, None);
}

#[test]
fn range_size_producer_returns_none_when_accept_ranges_none() {
    let h = headers_with_two(
        http::header::ACCEPT_RANGES,
        "none",
        http::header::CONTENT_RANGE,
        "bytes */4096",
    );
    let out = CpRangeSizeProducer.extract(ResponseClass::RangeNotSatisfiable, &h);
    assert_eq!(out, None);
}

#[test]
fn range_size_producer_returns_none_when_no_content_range() {
    let out = CpRangeSizeProducer.extract(ResponseClass::PartialContent, &HeaderMap::new());
    assert_eq!(out, None);
}

// --- CpRangeSatisfiableConsumer tests ---

#[test]
fn satisfiable_consumer_needs_content_range_size() {
    assert_eq!(
        CpRangeSatisfiableConsumer.needs(),
        ProducerOutputKind::ContentRangeSize
    );
}

#[test]
fn satisfiable_consumer_generates_two_specs_from_size() {
    let ctx = minimal_ctx();
    let out = ProducerOutput::ContentRangeSize(4096);
    let specs = CpRangeSatisfiableConsumer.generate(&ctx, &out);
    assert_eq!(
        specs.len(),
        2,
        "expected exactly 2 specs; got {}",
        specs.len()
    );
}

#[test]
fn satisfiable_consumer_probe_has_full_range_header() {
    let ctx = minimal_ctx();
    let out = ProducerOutput::ContentRangeSize(4096);
    let specs = CpRangeSatisfiableConsumer.generate(&ctx, &out);
    let ProbeSpec::Pair(pair) = &specs[0] else {
        panic!("expected Pair")
    };
    assert_eq!(
        pair.probe.headers.get("range").unwrap(),
        "bytes=0-4095",
        "first spec must be full range"
    );
}

#[test]
fn satisfiable_consumer_probe_has_last_byte_range_header() {
    let ctx = minimal_ctx();
    let out = ProducerOutput::ContentRangeSize(4096);
    let specs = CpRangeSatisfiableConsumer.generate(&ctx, &out);
    let ProbeSpec::Pair(pair) = &specs[1] else {
        panic!("expected Pair")
    };
    assert_eq!(
        pair.probe.headers.get("range").unwrap(),
        "bytes=4095-4095",
        "second spec must be last-byte range"
    );
}