parlov-analysis 0.3.0

Analysis engine trait and signal detection for parlov.
Documentation
//! Unit tests for existence oracle classifier — one per pattern arm.
//!
//! Every test asserts all five fields: verdict, severity, label, leaks, rfc_basis.

use super::classify;
use http::StatusCode;
use parlov_core::{OracleVerdict, Severity};

fn assert_pattern(
    baseline: u16,
    probe: u16,
    verdict: OracleVerdict,
    severity: Option<Severity>,
    label: Option<&str>,
    has_leaks: bool,
    rfc_basis: Option<&str>,
) {
    let b = StatusCode::from_u16(baseline).expect("valid status");
    let p = StatusCode::from_u16(probe).expect("valid status");
    let r = classify(b, p);
    assert_eq!(r.verdict, verdict, "{baseline}/{probe} verdict");
    assert_eq!(r.severity, severity, "{baseline}/{probe} severity");
    assert_eq!(r.label.as_deref(), label, "{baseline}/{probe} label");
    if has_leaks {
        assert!(r.leaks.is_some(), "{baseline}/{probe} expected leaks");
    } else {
        assert!(r.leaks.is_none(), "{baseline}/{probe} expected no leaks");
    }
    assert_eq!(r.rfc_basis.as_deref(), rfc_basis, "{baseline}/{probe} rfc_basis");
}

// ── Confirmed / High ────────────────────────────────────────────────

#[test]
fn forbidden_vs_not_found() {
    assert_pattern(
        403, 404,
        OracleVerdict::Confirmed, Some(Severity::High),
        Some("Authorization-based differential"),
        true, Some("RFC 9110 §15.5.4"),
    );
}

#[test]
fn ok_vs_not_found() {
    assert_pattern(
        200, 404,
        OracleVerdict::Confirmed, Some(Severity::High),
        Some("Direct access differential"),
        true, Some("RFC 9110 §15.3.1"),
    );
}

#[test]
fn unauthorized_vs_not_found() {
    assert_pattern(
        401, 404,
        OracleVerdict::Confirmed, Some(Severity::High),
        Some("Authentication-based differential"),
        true, Some("RFC 9110 §15.5.2"),
    );
}

#[test]
fn conflict_vs_created() {
    assert_pattern(
        409, 201,
        OracleVerdict::Confirmed, Some(Severity::High),
        Some("Conflict-based creation differential"),
        true, Some("RFC 9110 §15.5.10"),
    );
}

#[test]
fn conflict_vs_ok() {
    assert_pattern(
        409, 200,
        OracleVerdict::Confirmed, Some(Severity::High),
        Some("Conflict-based creation differential"),
        true, Some("RFC 9110 §15.5.10"),
    );
}

#[test]
fn conflict_vs_see_other() {
    assert_pattern(
        409, 303,
        OracleVerdict::Confirmed, Some(Severity::High),
        Some("Conflict-based creation differential"),
        true, Some("RFC 9110 §15.5.10"),
    );
}

#[test]
fn conflict_vs_accepted() {
    assert_pattern(
        409, 202,
        OracleVerdict::Confirmed, Some(Severity::High),
        Some("Conflict-based creation differential"),
        true, Some("RFC 9110 §15.5.10"),
    );
}

#[test]
fn unprocessable_vs_not_found() {
    assert_pattern(
        422, 404,
        OracleVerdict::Confirmed, Some(Severity::High),
        Some("Validation-path differential"),
        true, Some("RFC 9110 §15.5.21"),
    );
}

#[test]
fn unprocessable_vs_created() {
    assert_pattern(
        422, 201,
        OracleVerdict::Confirmed, Some(Severity::High),
        Some("Validation-path differential"),
        true, Some("RFC 9110 §9.3.4"),
    );
}

#[test]
fn partial_content_vs_not_found() {
    assert_pattern(
        206, 404,
        OracleVerdict::Confirmed, Some(Severity::High),
        Some("Range-request differential"),
        true, Some("RFC 9110 §15.3.7"),
    );
}

#[test]
fn not_modified_vs_not_found() {
    assert_pattern(
        304, 404,
        OracleVerdict::Confirmed, Some(Severity::High),
        Some("Conditional-request differential"),
        true, Some("RFC 9110 §15.4.5"),
    );
}

#[test]
fn not_acceptable_vs_not_found() {
    assert_pattern(
        406, 404,
        OracleVerdict::Confirmed, Some(Severity::High),
        Some("Content-negotiation differential"),
        true, Some("RFC 9110 §15.5.7"),
    );
}

#[test]
fn precondition_failed_vs_not_found() {
    assert_pattern(
        412, 404,
        OracleVerdict::Confirmed, Some(Severity::High),
        Some("Precondition-failed differential"),
        true, Some("RFC 9110 §13.1.1"),
    );
}

#[test]
fn unsupported_media_type_vs_not_found() {
    assert_pattern(
        415, 404,
        OracleVerdict::Confirmed, Some(Severity::High),
        Some("Media-type differential"),
        true, Some("RFC 9110 §15.5.16"),
    );
}

#[test]
fn conflict_vs_not_found() {
    assert_pattern(
        409, 404,
        OracleVerdict::Confirmed, Some(Severity::High),
        Some("State-conflict differential"),
        true, Some("RFC 9110 §15.5.10"),
    );
}

#[test]
fn conflict_vs_no_content() {
    assert_pattern(
        409, 204,
        OracleVerdict::Confirmed, Some(Severity::High),
        Some("Conflict-based differential"),
        true, Some("RFC 9110 §15.5.10"),
    );
}

// ── Confirmed / Medium ──────────────────────────────────────────────

#[test]
fn gone_vs_not_found() {
    assert_pattern(
        410, 404,
        OracleVerdict::Confirmed, Some(Severity::Medium),
        Some("Tombstone differential"),
        true, Some("RFC 9110 §15.5.11"),
    );
}

#[test]
fn internal_server_error_vs_not_found() {
    assert_pattern(
        500, 404,
        OracleVerdict::Confirmed, Some(Severity::Medium),
        Some("Crash-path differential"),
        true, Some("RFC 9110 §15.6.1"),
    );
}

#[test]
fn no_content_vs_not_found() {
    assert_pattern(
        204, 404,
        OracleVerdict::Confirmed, Some(Severity::Medium),
        Some("No-content differential"),
        true, Some("RFC 9110 §9.3.2"),
    );
}

#[test]
fn method_not_allowed_vs_not_found() {
    assert_pattern(
        405, 404,
        OracleVerdict::Confirmed, Some(Severity::Medium),
        Some("Method-restriction differential"),
        true, Some("RFC 9110 §15.5.6"),
    );
}

#[test]
fn moved_permanently_vs_not_found() {
    assert_pattern(
        301, 404,
        OracleVerdict::Confirmed, Some(Severity::Medium),
        Some("Redirect-based differential"),
        true, Some("RFC 9110 §15.4.2"),
    );
}

#[test]
fn range_not_satisfiable_vs_not_found() {
    assert_pattern(
        416, 404,
        OracleVerdict::Confirmed, Some(Severity::Medium),
        Some("Range-not-satisfiable differential"),
        true, Some("RFC 9110 §15.5.17"),
    );
}

#[test]
fn payload_too_large_vs_not_found() {
    assert_pattern(
        413, 404,
        OracleVerdict::Confirmed, Some(Severity::Medium),
        Some("Payload-size differential"),
        true, Some("RFC 9110 §15.5.14"),
    );
}

#[test]
fn length_required_vs_not_found() {
    assert_pattern(
        411, 404,
        OracleVerdict::Confirmed, Some(Severity::Medium),
        Some("Length-required differential"),
        true, Some("RFC 9110 §15.5.12"),
    );
}

#[test]
fn accepted_vs_not_found() {
    assert_pattern(
        202, 404,
        OracleVerdict::Confirmed, Some(Severity::Medium),
        Some("Async-acceptance differential"),
        true, Some("RFC 9110 §15.3.3"),
    );
}

// ── Likely / Medium ─────────────────────────────────────────────────

#[test]
fn payment_required_vs_not_found() {
    assert_pattern(
        402, 404,
        OracleVerdict::Likely, Some(Severity::Medium),
        Some("Payment-gate differential"),
        true, Some("RFC 9110 §15.5.3"),
    );
}

#[test]
fn bad_request_vs_created() {
    assert_pattern(
        400, 201,
        OracleVerdict::Likely, Some(Severity::Medium),
        Some("Client-error creation differential"),
        true, Some("RFC 9110 §15.5.1"),
    );
}

#[test]
fn bad_request_vs_ok() {
    assert_pattern(
        400, 200,
        OracleVerdict::Likely, Some(Severity::Medium),
        Some("Client-error differential"),
        true, Some("RFC 9110 §15.5.1"),
    );
}

#[test]
fn too_many_requests_vs_not_found() {
    assert_pattern(
        429, 404,
        OracleVerdict::Likely, Some(Severity::Medium),
        Some("Rate-limit-based differential"),
        true, Some("RFC 6585 §4"),
    );
}

// ── NotPresent (same status) ────────────────────────────────────────

#[test]
fn same_status_not_present() {
    assert_pattern(
        404, 404,
        OracleVerdict::NotPresent, None,
        None, false, None,
    );
}

#[test]
fn same_status_200_not_present() {
    assert_pattern(
        200, 200,
        OracleVerdict::NotPresent, None,
        None, false, None,
    );
}

// ── Wildcard (unclassified differential) ────────────────────────────

#[test]
fn unrecognised_diff_likely_low() {
    assert_pattern(
        418, 404,
        OracleVerdict::Likely, Some(Severity::Low),
        None, false, None,
    );
}

#[test]
fn unrecognised_diff_503_vs_200() {
    assert_pattern(
        503, 200,
        OracleVerdict::Likely, Some(Severity::Low),
        None, false, None,
    );
}