#![deny(clippy::all)]
use http::{HeaderMap, Method};
use parlov_analysis::existence::ExistenceAnalyzer;
use parlov_analysis::{Analyzer, SampleDecision};
use parlov_core::{DifferentialSet, OracleResult, OracleVerdict, Vector};
use parlov_elicit::{ProbePair, ProbeSpec, RiskLevel, ScanContext, generate_plan};
use parlov_probe::http::HttpProbe;
use parlov_probe::Probe;
mod fixtures {
pub mod get_server;
}
async fn collect_pair(pair: &ProbePair) -> OracleResult {
let client = HttpProbe::new();
let analyzer = ExistenceAnalyzer;
let mut diff_set = DifferentialSet {
baseline: Vec::new(),
probe: Vec::new(),
technique: pair.technique.clone(),
};
loop {
let b = client.execute(&pair.baseline).await.expect("baseline failed");
let p = client.execute(&pair.probe).await.expect("probe failed");
diff_set.baseline.push(b);
diff_set.probe.push(p);
if let SampleDecision::Complete(result) = analyzer.evaluate(&diff_set) {
return *result;
}
}
}
fn make_ctx(base_url: &str, route: &str) -> ScanContext {
ScanContext {
target: format!("{base_url}{route}/{{id}}"),
baseline_id: "42".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,
}
}
fn find_pair(plan: &[ProbeSpec], id: &str, method: &Method) -> Option<ProbePair> {
plan.iter().find_map(|spec| {
if let ProbeSpec::Pair(p) = spec {
if p.metadata.strategy_id == id && p.baseline.method == *method {
return Some(p.clone());
}
}
None
})
}
#[test]
fn safe_plan_includes_all_8_cache_probing_strategies() {
let ctx = make_ctx("http://localhost:0", "/cp/conditional");
let plan = generate_plan(&ctx);
let cp_ids: Vec<&str> = plan
.iter()
.filter(|s| s.technique().vector == Vector::CacheProbing)
.map(|s| s.technique().id)
.collect();
for expected in &[
"cp-if-none-match",
"cp-if-modified-since",
"cp-if-match",
"cp-if-unmodified-since",
"cp-range",
"cp-range-unsatisfiable",
"cp-if-range",
"cp-accept",
] {
assert!(
cp_ids.contains(expected),
"cache-probing strategy '{expected}' missing from plan; got: {cp_ids:?}"
);
}
}
#[tokio::test]
async fn cp_if_none_match_vs_conditional_confirmed() {
let addr = fixtures::get_server::spawn().await;
let ctx = make_ctx(&format!("http://{addr}"), "/cp/conditional");
let plan = generate_plan(&ctx);
let pair = find_pair(&plan, "cp-if-none-match", &Method::GET)
.expect("cp-if-none-match GET spec must be in safe plan");
let result = collect_pair(&pair).await;
assert_eq!(
result.verdict,
OracleVerdict::Confirmed,
"cp-if-none-match on /cp/conditional should be Confirmed; evidence: {:?}",
result.signals
);
}
#[tokio::test]
async fn cp_if_modified_since_vs_conditional_confirmed() {
let addr = fixtures::get_server::spawn().await;
let ctx = make_ctx(&format!("http://{addr}"), "/cp/conditional");
let plan = generate_plan(&ctx);
let pair = find_pair(&plan, "cp-if-modified-since", &Method::GET)
.expect("cp-if-modified-since GET spec must be in safe plan");
let result = collect_pair(&pair).await;
assert_eq!(
result.verdict,
OracleVerdict::Confirmed,
"cp-if-modified-since on /cp/conditional should be Confirmed; evidence: {:?}",
result.signals
);
}
#[tokio::test]
async fn cp_if_match_vs_conditional_confirmed() {
let addr = fixtures::get_server::spawn().await;
let ctx = make_ctx(&format!("http://{addr}"), "/cp/conditional");
let plan = generate_plan(&ctx);
let pair = find_pair(&plan, "cp-if-match", &Method::GET)
.expect("cp-if-match GET spec must be in safe plan");
let result = collect_pair(&pair).await;
assert_eq!(
result.verdict,
OracleVerdict::Confirmed,
"cp-if-match on /cp/conditional should be Confirmed; evidence: {:?}",
result.signals
);
}
#[tokio::test]
async fn cp_if_unmodified_since_vs_conditional_confirmed() {
let addr = fixtures::get_server::spawn().await;
let ctx = make_ctx(&format!("http://{addr}"), "/cp/conditional");
let plan = generate_plan(&ctx);
let pair = find_pair(&plan, "cp-if-unmodified-since", &Method::GET)
.expect("cp-if-unmodified-since GET spec must be in safe plan");
let result = collect_pair(&pair).await;
assert_eq!(
result.verdict,
OracleVerdict::Confirmed,
"cp-if-unmodified-since on /cp/conditional should be Confirmed; evidence: {:?}",
result.signals
);
}
#[tokio::test]
async fn cp_range_satisfiable_vs_range_confirmed() {
let addr = fixtures::get_server::spawn().await;
let ctx = make_ctx(&format!("http://{addr}"), "/cp/range");
let plan = generate_plan(&ctx);
let pair = find_pair(&plan, "cp-range", &Method::GET)
.expect("cp-range GET spec must be in safe plan");
let result = collect_pair(&pair).await;
assert_eq!(
result.verdict,
OracleVerdict::Confirmed,
"cp-range on /cp/range should be Confirmed; evidence: {:?}",
result.signals
);
}
#[tokio::test]
async fn cp_range_unsatisfiable_vs_range_confirmed() {
let addr = fixtures::get_server::spawn().await;
let ctx = make_ctx(&format!("http://{addr}"), "/cp/range");
let plan = generate_plan(&ctx);
let pair = find_pair(&plan, "cp-range-unsatisfiable", &Method::GET)
.expect("cp-range-unsatisfiable GET spec must be in safe plan");
let result = collect_pair(&pair).await;
assert_eq!(
result.verdict,
OracleVerdict::Confirmed,
"cp-range-unsatisfiable on /cp/range should be Confirmed; evidence: {:?}",
result.signals
);
}
#[tokio::test]
async fn cp_if_range_vs_range_confirmed() {
let addr = fixtures::get_server::spawn().await;
let ctx = make_ctx(&format!("http://{addr}"), "/cp/range");
let plan = generate_plan(&ctx);
let pair = find_pair(&plan, "cp-if-range", &Method::GET)
.expect("cp-if-range GET spec must be in safe plan");
let result = collect_pair(&pair).await;
assert_eq!(
result.verdict,
OracleVerdict::Confirmed,
"cp-if-range on /cp/range should be Confirmed; evidence: {:?}",
result.signals
);
}
#[tokio::test]
async fn cp_accept_vs_negotiated_confirmed() {
let addr = fixtures::get_server::spawn().await;
let ctx = make_ctx(&format!("http://{addr}"), "/cp/negotiated");
let plan = generate_plan(&ctx);
let pair = find_pair(&plan, "cp-accept", &Method::GET)
.expect("cp-accept GET spec must be in safe plan");
let result = collect_pair(&pair).await;
assert_eq!(
result.verdict,
OracleVerdict::Confirmed,
"cp-accept on /cp/negotiated should be Confirmed; evidence: {:?}",
result.signals
);
}
#[tokio::test]
async fn cp_if_none_match_vs_normalized_not_present() {
let addr = fixtures::get_server::spawn().await;
let ctx = make_ctx(&format!("http://{addr}"), "/cp/normalized");
let plan = generate_plan(&ctx);
let pair = find_pair(&plan, "cp-if-none-match", &Method::GET)
.expect("cp-if-none-match GET spec must be in safe plan");
let result = collect_pair(&pair).await;
assert_eq!(
result.verdict,
OracleVerdict::NotPresent,
"cp-if-none-match on /cp/normalized should be NotPresent; evidence: {:?}",
result.signals
);
}