parlov 0.4.0

HTTP oracle detection tool — systematic probing for RFC-compliant information leakage.
Documentation
//! Integration tests for the scan pipeline.
//!
//! Spawns a minimal axum server on `127.0.0.1:0`, builds a `ScanContext`, calls
//! `generate_plan`, drives the safe-strategy probes, and asserts that the
//! accept and if-none-match strategies produce `Confirmed` findings.

#![deny(clippy::all)]

use http::{HeaderMap, Method};
use parlov_core::OracleVerdict;
use parlov_elicit::{generate_plan, ProbePair, ProbeSpec, RiskLevel, ScanContext};
use parlov_probe::http::HttpProbe;
use parlov_probe::Probe;
use parlov_analysis::{Analyzer, SampleDecision};
use parlov_analysis::existence::ExistenceAnalyzer;
use parlov_core::{OracleResult, ProbeSet};

mod fixtures {
    pub mod scan_server;
}

// --- helpers ---

async fn collect_pair(pair: &ProbePair) -> OracleResult {
    let client = HttpProbe::new();
    let analyzer = ExistenceAnalyzer;
    let mut probe_set = ProbeSet { baseline: Vec::new(), probe: Vec::new() };

    loop {
        let b = client
            .execute(&pair.baseline)
            .await
            .expect("baseline request failed");
        let p = client
            .execute(&pair.probe)
            .await
            .expect("probe request failed");
        probe_set.baseline.push(b);
        probe_set.probe.push(p);

        if let SampleDecision::Complete(result) = analyzer.evaluate(&probe_set) {
            return result;
        }
    }
}

fn make_ctx(base_url: &str) -> ScanContext {
    ScanContext {
        target: format!("{base_url}/oracle/{{id}}"),
        baseline_id: "1001".to_owned(),
        probe_id: "9999".to_owned(),
        headers: HeaderMap::new(),
        max_risk: RiskLevel::Safe,
        known_duplicate: None,
        state_field: None,
        alt_credential: None,
        body_template: None,
    }
}

// --- tests ---

#[tokio::test]
async fn accept_strategy_produces_confirmed_finding() {
    let addr = fixtures::scan_server::spawn().await;
    let base_url = format!("http://{addr}");
    let ctx = make_ctx(&base_url);
    let plan = generate_plan(&ctx);

    let accept_pair = plan.iter().find_map(|spec| {
        if let ProbeSpec::Pair(p) = spec {
            if p.metadata.strategy_id == "accept-elicit" && p.baseline.method == Method::GET {
                return Some(p.clone());
            }
        }
        None
    });

    let pair = accept_pair.expect("accept-elicit GET spec must be in safe plan");
    let result = collect_pair(&pair).await;
    assert_eq!(
        result.verdict,
        OracleVerdict::Confirmed,
        "accept-elicit should produce Confirmed; evidence: {:?}",
        result.evidence
    );
}

#[tokio::test]
async fn if_none_match_strategy_produces_confirmed_finding() {
    let addr = fixtures::scan_server::spawn().await;
    let base_url = format!("http://{addr}");
    let ctx = make_ctx(&base_url);
    let plan = generate_plan(&ctx);

    let inmatch_pair = plan.iter().find_map(|spec| {
        if let ProbeSpec::Pair(p) = spec {
            if p.metadata.strategy_id == "if-none-match-elicit"
                && p.baseline.method == Method::GET
            {
                return Some(p.clone());
            }
        }
        None
    });

    let pair = inmatch_pair.expect("if-none-match-elicit GET spec must be in safe plan");
    let result = collect_pair(&pair).await;
    assert_eq!(
        result.verdict,
        OracleVerdict::Confirmed,
        "if-none-match-elicit should produce Confirmed; evidence: {:?}",
        result.evidence
    );
}

#[tokio::test]
async fn safe_plan_generates_specs_for_minimal_ctx() {
    let addr = fixtures::scan_server::spawn().await;
    let base_url = format!("http://{addr}");
    let ctx = make_ctx(&base_url);
    let plan = generate_plan(&ctx);
    assert!(!plan.is_empty(), "safe plan must contain at least one spec");
}