#![deny(clippy::all)]
mod fixtures {
pub mod get_server;
pub mod post_server;
pub mod put_server;
}
use bytes::Bytes;
use http::{HeaderMap, HeaderValue, Method, header};
use parlov_analysis::existence::ExistenceAnalyzer;
use parlov_analysis::{Analyzer, SampleDecision};
use parlov_core::{
DifferentialSet, NormativeStrength, OracleClass, OracleResult, OracleVerdict,
ProbeDefinition, Severity, Technique, Vector,
};
use parlov_probe::http::HttpProbe;
use parlov_probe::Probe;
use reqwest::redirect;
fn json_headers() -> HeaderMap {
let mut h = HeaderMap::new();
h.insert(header::CONTENT_TYPE, HeaderValue::from_static("application/json"));
h
}
fn make_def(url: String, method: Method, body: Option<Bytes>) -> ProbeDefinition {
let headers = if method == Method::GET { HeaderMap::new() } else { json_headers() };
ProbeDefinition { url, method, headers, body }
}
fn rd_technique() -> Technique {
Technique {
id: "test-redirect-diff",
name: "Test redirect diff",
oracle_class: OracleClass::Existence,
vector: Vector::RedirectDiff,
strength: NormativeStrength::Should,
}
}
async fn collect_rd(
baseline_url: String,
probe_url: String,
method: Method,
body: Option<Bytes>,
) -> OracleResult {
let client = HttpProbe::with_client(
reqwest::Client::builder()
.redirect(redirect::Policy::none())
.build()
.expect("client build failed"),
);
let analyzer = ExistenceAnalyzer;
let mut ds = DifferentialSet {
baseline: Vec::new(),
probe: Vec::new(),
technique: rd_technique(),
};
loop {
let bd = make_def(baseline_url.clone(), method.clone(), body.clone());
let pd = make_def(probe_url.clone(), method.clone(), body.clone());
ds.baseline.push(client.execute(&bd).await.expect("baseline failed"));
ds.probe.push(client.execute(&pd).await.expect("probe failed"));
if let SampleDecision::Complete(result) = analyzer.evaluate(&ds) {
return *result;
}
}
}
async fn get_rd(addr: std::net::SocketAddr, path: &str) -> OracleResult {
collect_rd(
format!("http://{addr}{}", path.replace("{id}", "42")),
format!("http://{addr}{}", path.replace("{id}", "999")),
Method::GET,
None,
)
.await
}
async fn post_rd(addr: std::net::SocketAddr, path: &str) -> OracleResult {
collect_rd(
format!("http://{addr}{}", path.replace("{id}", "42")),
format!("http://{addr}{}", path.replace("{id}", "999")),
Method::POST,
Some(Bytes::from_static(b"{}")),
)
.await
}
async fn put_rd(addr: std::net::SocketAddr, path: &str) -> OracleResult {
collect_rd(
format!("http://{addr}{}", path.replace("{id}", "42")),
format!("http://{addr}{}", path.replace("{id}", "999")),
Method::PUT,
Some(Bytes::from_static(b"{\"name\":\"updated\"}")),
)
.await
}
#[tokio::test]
async fn rd_slash_append_301_vs_404_confirmed() {
let r = get_rd(fixtures::get_server::spawn().await, "/rd/slash/{id}").await;
assert_eq!(r.verdict, OracleVerdict::Confirmed);
assert!(r.severity.is_some());
}
#[tokio::test]
async fn rd_slash_append_blanket_not_present() {
let r = get_rd(fixtures::get_server::spawn().await, "/rd/blanket/{id}").await;
assert_eq!(r.verdict, OracleVerdict::NotPresent);
assert_eq!(r.severity, None);
}
#[tokio::test]
async fn rd_slash_strip_301_vs_404_confirmed() {
let r = get_rd(fixtures::get_server::spawn().await, "/rd/strip/{id}/").await;
assert_eq!(r.verdict, OracleVerdict::Confirmed);
assert!(r.severity.is_some());
}
#[tokio::test]
async fn rd_slash_strip_blanket_not_present() {
let r = get_rd(fixtures::get_server::spawn().await, "/rd/blanket/{id}").await;
assert_eq!(r.verdict, OracleVerdict::NotPresent);
assert_eq!(r.severity, None);
}
#[tokio::test]
async fn rd_case_variation_301_vs_404_confirmed() {
let r = get_rd(fixtures::get_server::spawn().await, "/RD/CASE/{id}").await;
assert_eq!(r.verdict, OracleVerdict::Confirmed);
assert!(r.severity.is_some());
}
#[tokio::test]
async fn rd_case_variation_blanket_not_present() {
let r = get_rd(fixtures::get_server::spawn().await, "/rd/blanket/{id}").await;
assert_eq!(r.verdict, OracleVerdict::NotPresent);
assert_eq!(r.severity, None);
}
#[tokio::test]
async fn rd_double_slash_301_vs_404_confirmed() {
let r = get_rd(fixtures::get_server::spawn().await, "//rd/double/{id}").await;
assert_eq!(r.verdict, OracleVerdict::Confirmed);
assert!(r.severity.is_some());
}
#[tokio::test]
async fn rd_double_slash_blanket_not_present() {
let r = get_rd(fixtures::get_server::spawn().await, "/rd/blanket/{id}").await;
assert_eq!(r.verdict, OracleVerdict::NotPresent);
assert_eq!(r.severity, None);
}
#[tokio::test]
async fn rd_percent_encoding_301_vs_404_confirmed() {
let r = get_rd(fixtures::get_server::spawn().await, "/rd/percent/{id}").await;
assert_eq!(r.verdict, OracleVerdict::Confirmed);
assert!(r.severity.is_some());
}
#[tokio::test]
async fn rd_percent_encoding_blanket_not_present() {
let r = get_rd(fixtures::get_server::spawn().await, "/rd/blanket/{id}").await;
assert_eq!(r.verdict, OracleVerdict::NotPresent);
assert_eq!(r.severity, None);
}
#[tokio::test]
async fn rd_post_to_303_confirmed() {
let r = post_rd(fixtures::post_server::spawn().await, "/rd/post-303/{id}").await;
assert_eq!(r.verdict, OracleVerdict::Confirmed);
assert!(r.severity.is_some());
}
#[tokio::test]
async fn rd_post_to_303_normalized_not_present() {
let r = post_rd(fixtures::post_server::spawn().await, "/rd/post-normalized/{id}").await;
assert_eq!(r.verdict, OracleVerdict::NotPresent);
assert_eq!(r.severity, None);
}
#[tokio::test]
async fn rd_put_to_303_confirmed() {
let r = put_rd(fixtures::put_server::spawn().await, "/rd/put-303/{id}").await;
assert_eq!(r.verdict, OracleVerdict::Confirmed);
assert!(r.severity.is_some());
}
#[tokio::test]
async fn rd_put_to_303_normalized_not_present() {
let r = put_rd(fixtures::put_server::spawn().await, "/rd/put-normalized/{id}").await;
assert_eq!(r.verdict, OracleVerdict::NotPresent);
assert_eq!(r.severity, None);
}
#[tokio::test]
#[ignore = "RdProtocolUpgrade requires TLS infrastructure not available in test environment"]
async fn rd_protocol_upgrade_stub() {
}
#[tokio::test]
async fn rd_slash_append_severity_at_least_low() {
let r = get_rd(fixtures::get_server::spawn().await, "/rd/slash/{id}").await;
assert_eq!(r.verdict, OracleVerdict::Confirmed);
assert!(
r.severity == Some(Severity::Low)
|| r.severity == Some(Severity::Medium)
|| r.severity == Some(Severity::High),
"expected at least Low severity, got {:?}",
r.severity
);
}