parlov 0.3.0

HTTP oracle detection tool — systematic probing for RFC-compliant information leakage.
//! Integration tests for the DELETE existence oracle pipeline.
//!
//! Each test spawns an isolated RFC-compliant DELETE server, drives the adaptive
//! sampling loop via `evaluate`, and asserts the final verdict and severity.

#![deny(clippy::all)]

mod fixtures {
    pub mod delete_server;
}

use fixtures::delete_server::spawn;
use http::{HeaderMap, Method};
use parlov_analysis::existence::ExistenceAnalyzer;
use parlov_analysis::{Analyzer, SampleDecision};
use parlov_core::{OracleResult, OracleVerdict, ProbeDefinition, ProbeSet, Severity};
use parlov_probe::http::HttpProbe;
use parlov_probe::Probe;

// --- helpers ---

fn def(url: String) -> ProbeDefinition {
    ProbeDefinition {
        url,
        method: Method::DELETE,
        headers: HeaderMap::new(),
        body: None,
    }
}

/// Adaptive sampling loop matching the binary's `collect_until_verdict` pattern.
async fn collect_until_verdict(
    route_template: &str,
    baseline_id: &str,
    probe_id: &str,
    addr: std::net::SocketAddr,
) -> OracleResult {
    let client = HttpProbe::new();
    let analyzer = ExistenceAnalyzer;
    let baseline_url = format!("http://{addr}{}", route_template.replace("{id}", baseline_id));
    let probe_url = format!("http://{addr}{}", route_template.replace("{id}", probe_id));
    let mut probe_set = ProbeSet {
        baseline: Vec::new(),
        probe: Vec::new(),
    };

    loop {
        let b = client.execute(&def(baseline_url.clone())).await.expect("baseline request failed");
        let p = client.execute(&def(probe_url.clone())).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;
        }
    }
}

// --- 405 vs 404 → Confirmed / Medium ---

#[tokio::test]
async fn delete_405_vs_404_confirmed_medium() {
    let addr = spawn().await;
    let result = collect_until_verdict("/resource/{id}", "42", "999", addr).await;
    assert_eq!(result.verdict, OracleVerdict::Confirmed);
    assert_eq!(result.severity, Some(Severity::Medium));
}

// --- normalized (always 404) → NotPresent ---

#[tokio::test]
async fn delete_normalized_not_present() {
    let addr = spawn().await;
    let result = collect_until_verdict("/normalized/{id}", "42", "999", addr).await;
    assert_eq!(result.verdict, OracleVerdict::NotPresent);
    assert_eq!(result.severity, None);
}

// --- 204 vs 404 → Confirmed / Medium ---

#[tokio::test]
async fn delete_204_vs_404_confirmed_medium() {
    let addr = spawn().await;
    let result = collect_until_verdict("/deletable/{id}", "42", "999", addr).await;
    assert_eq!(result.verdict, OracleVerdict::Confirmed);
    assert_eq!(result.severity, Some(Severity::Medium));
    assert!(result.label.is_some());
    assert!(result.rfc_basis.is_some());
}

// --- 200 vs 404 → Confirmed / High ---

#[tokio::test]
async fn delete_200_vs_404_confirmed_high() {
    let addr = spawn().await;
    let result = collect_until_verdict("/deletable-200/{id}", "42", "999", addr).await;
    assert_eq!(result.verdict, OracleVerdict::Confirmed);
    assert_eq!(result.severity, Some(Severity::High));
    assert!(result.label.is_some());
    assert!(result.rfc_basis.is_some());
}

// --- 202 vs 404 → Confirmed / Medium ---

#[tokio::test]
async fn delete_202_vs_404_confirmed_medium() {
    let addr = spawn().await;
    let result = collect_until_verdict("/async-delete/{id}", "42", "999", addr).await;
    assert_eq!(result.verdict, OracleVerdict::Confirmed);
    assert_eq!(result.severity, Some(Severity::Medium));
    assert!(result.label.is_some());
    assert!(result.rfc_basis.is_some());
}

// --- 409 vs 404 → Confirmed / High ---

#[tokio::test]
async fn delete_409_vs_404_confirmed_high() {
    let addr = spawn().await;
    let result = collect_until_verdict("/has-deps/{id}", "42", "999", addr).await;
    assert_eq!(result.verdict, OracleVerdict::Confirmed);
    assert_eq!(result.severity, Some(Severity::High));
    assert!(result.label.is_some());
    assert!(result.rfc_basis.is_some());
}

// --- 403 vs 404 → Confirmed / High ---

#[tokio::test]
async fn delete_403_vs_404_confirmed_high() {
    let addr = spawn().await;
    let result = collect_until_verdict("/forbidden/{id}", "42", "999", addr).await;
    assert_eq!(result.verdict, OracleVerdict::Confirmed);
    assert_eq!(result.severity, Some(Severity::High));
    assert!(result.label.is_some());
    assert!(result.rfc_basis.is_some());
}