use super::*;
fn confirmed_result_with_metadata() -> OracleResult {
OracleResult {
class: OracleClass::Existence,
verdict: OracleVerdict::Confirmed,
severity: Some(Severity::High),
confidence: 0,
impact_class: None,
reasons: vec![],
signals: vec![Signal {
kind: SignalKind::StatusCodeDiff,
evidence: "403 (baseline) vs 404 (probe)".into(),
rfc_basis: None,
}],
technique_id: None,
vector: None,
normative_strength: None,
label: Some("Authorization-based differential".into()),
leaks: Some("Resource existence confirmed to low-privilege callers".into()),
rfc_basis: Some("RFC 9110 \u{00a7}15.5.4".into()),
}
}
fn not_present_result() -> OracleResult {
OracleResult {
class: OracleClass::Existence,
verdict: OracleVerdict::NotPresent,
severity: None,
confidence: 0,
impact_class: None,
reasons: vec![],
signals: vec![Signal {
kind: SignalKind::StatusCodeDiff,
evidence: "404 (baseline) vs 404 (probe)".into(),
rfc_basis: None,
}],
technique_id: None,
vector: None,
normative_strength: None,
label: None,
leaks: None,
rfc_basis: None,
}
}
#[test]
fn serialize_confirmed_includes_metadata_fields() {
let result = confirmed_result_with_metadata();
let json = serde_json::to_value(&result).expect("serialization failed");
assert_eq!(json["label"], "Authorization-based differential");
assert_eq!(
json["leaks"],
"Resource existence confirmed to low-privilege callers"
);
assert_eq!(json["rfc_basis"], "RFC 9110 \u{00a7}15.5.4");
}
#[test]
fn serialize_not_present_omits_none_metadata() {
let result = not_present_result();
let json = serde_json::to_value(&result).expect("serialization failed");
assert!(!json
.as_object()
.expect("expected object")
.contains_key("label"));
assert!(!json
.as_object()
.expect("expected object")
.contains_key("leaks"));
assert!(!json
.as_object()
.expect("expected object")
.contains_key("rfc_basis"));
}
#[test]
fn roundtrip_confirmed_preserves_metadata() {
let original = confirmed_result_with_metadata();
let json = serde_json::to_string(&original).expect("serialization failed");
let deserialized: OracleResult = serde_json::from_str(&json).expect("deserialization failed");
assert_eq!(deserialized.label, original.label);
assert_eq!(deserialized.leaks, original.leaks);
assert_eq!(deserialized.rfc_basis, original.rfc_basis);
}
#[test]
fn roundtrip_not_present_preserves_none_metadata() {
let original = not_present_result();
let json = serde_json::to_string(&original).expect("serialization failed");
let deserialized: OracleResult = serde_json::from_str(&json).expect("deserialization failed");
assert_eq!(deserialized.label, None);
assert_eq!(deserialized.leaks, None);
assert_eq!(deserialized.rfc_basis, None);
}
#[test]
fn deserialize_minimal_json_defaults_to_none() {
let minimal = r#"{
"class": "Existence",
"verdict": "Confirmed",
"severity": "High"
}"#;
let result: OracleResult = serde_json::from_str(minimal).expect("deserialization failed");
assert_eq!(result.label, None);
assert_eq!(result.leaks, None);
assert_eq!(result.rfc_basis, None);
assert!(result.signals.is_empty());
assert_eq!(result.technique_id, None);
assert_eq!(result.confidence, 0);
assert_eq!(result.impact_class, None);
assert!(result.reasons.is_empty());
}
#[test]
fn oracle_result_with_technique_context_serializes() {
let result = OracleResult {
class: OracleClass::Existence,
verdict: OracleVerdict::Confirmed,
severity: Some(Severity::High),
confidence: 0,
impact_class: None,
reasons: vec![],
signals: vec![Signal {
kind: SignalKind::StatusCodeDiff,
evidence: "304 vs 404".into(),
rfc_basis: Some("RFC 9110 \u{00a7}13.1.2".into()),
}],
technique_id: Some("if-none-match".into()),
vector: Some(Vector::CacheProbing),
normative_strength: Some(NormativeStrength::Must),
label: None,
leaks: None,
rfc_basis: None,
};
let json = serde_json::to_value(&result).expect("serialization failed");
assert_eq!(json["technique_id"], "if-none-match");
assert_eq!(json["vector"], "CacheProbing");
assert_eq!(json["normative_strength"], "Must");
assert_eq!(json["signals"][0]["kind"], "StatusCodeDiff");
assert_eq!(json["signals"][0]["evidence"], "304 vs 404");
assert_eq!(json["signals"][0]["rfc_basis"], "RFC 9110 \u{00a7}13.1.2");
}
#[test]
fn oracle_result_roundtrip_with_technique_context() {
let original = OracleResult {
class: OracleClass::Existence,
verdict: OracleVerdict::Likely,
severity: Some(Severity::Medium),
confidence: 0,
impact_class: None,
reasons: vec![],
signals: vec![Signal {
kind: SignalKind::HeaderPresence,
evidence: "ETag present in baseline, absent in probe".into(),
rfc_basis: None,
}],
technique_id: Some("get-200-404".into()),
vector: Some(Vector::StatusCodeDiff),
normative_strength: Some(NormativeStrength::Should),
label: Some("Status code differential".into()),
leaks: Some("Resource existence".into()),
rfc_basis: Some("RFC 9110 \u{00a7}15.5.5".into()),
};
let json = serde_json::to_string(&original).expect("serialization failed");
let back: OracleResult = serde_json::from_str(&json).expect("deserialization failed");
assert_eq!(back.technique_id, original.technique_id);
assert_eq!(back.vector, original.vector);
assert_eq!(back.normative_strength, original.normative_strength);
assert_eq!(back.signals.len(), 1);
assert_eq!(back.signals[0].kind, SignalKind::HeaderPresence);
}
#[test]
fn primary_evidence_returns_status_code_diff() {
let result = confirmed_result_with_metadata();
assert_eq!(result.primary_evidence(), "403 (baseline) vs 404 (probe)");
}
#[test]
fn primary_evidence_falls_back_to_first_signal() {
let result = OracleResult {
class: OracleClass::Existence,
verdict: OracleVerdict::Confirmed,
severity: Some(Severity::Medium),
confidence: 0,
impact_class: None,
reasons: vec![],
signals: vec![Signal {
kind: SignalKind::HeaderPresence,
evidence: "etag present in baseline".into(),
rfc_basis: None,
}],
technique_id: None,
vector: None,
normative_strength: None,
label: None,
leaks: None,
rfc_basis: None,
};
assert_eq!(result.primary_evidence(), "etag present in baseline");
}
#[test]
fn primary_evidence_returns_dash_when_empty() {
let result = not_present_result();
let mut empty = result;
empty.signals.clear();
assert_eq!(empty.primary_evidence(), "\u{2014}");
}
#[test]
fn signal_kind_copy_and_eq() {
let a = SignalKind::StatusCodeDiff;
let b = a;
assert_eq!(a, b);
}
#[test]
fn vector_copy_and_eq() {
let a = Vector::CacheProbing;
let b = a;
assert_eq!(a, b);
}
#[test]
fn normative_strength_copy_and_eq() {
let a = NormativeStrength::Must;
let b = a;
assert_eq!(a, b);
assert_ne!(a, NormativeStrength::May);
}
#[test]
fn technique_clone() {
let t = Technique {
id: "test",
name: "Test technique",
oracle_class: OracleClass::Existence,
vector: Vector::StatusCodeDiff,
strength: NormativeStrength::Must,
normalization_weight: Some(0.2),
inverted_signal_weight: None,
method_relevant: false,
parser_relevant: false,
applicability: always_applicable,
contradiction_surface: SignalSurface::Status,
};
let t2 = t;
assert_eq!(t2.id, "test");
assert_eq!(t2.vector, Vector::StatusCodeDiff);
}
#[test]
fn probe_exchange_pairs_request_and_response() {
let exchange = ProbeExchange {
request: ProbeDefinition {
url: "https://example.com/resource/1".into(),
method: http::Method::GET,
headers: HeaderMap::new(),
body: None,
},
response: ResponseSurface {
status: http::StatusCode::OK,
headers: HeaderMap::new(),
body: Bytes::new(),
timing_ns: 1_000_000,
},
};
assert_eq!(exchange.request.url, "https://example.com/resource/1");
assert_eq!(exchange.response.status, http::StatusCode::OK);
}
#[test]
fn differential_set_carries_technique() {
let technique = Technique {
id: "get-200-404",
name: "GET 200/404",
oracle_class: OracleClass::Existence,
vector: Vector::StatusCodeDiff,
strength: NormativeStrength::Must,
normalization_weight: Some(0.2),
inverted_signal_weight: None,
method_relevant: false,
parser_relevant: false,
applicability: always_applicable,
contradiction_surface: SignalSurface::Status,
};
let ds = DifferentialSet {
baseline: vec![],
probe: vec![],
canonical: None,
technique,
};
assert_eq!(ds.technique.id, "get-200-404");
assert_eq!(ds.technique.strength, NormativeStrength::Must);
}
#[test]
fn technique_without_target_signals_constructs() {
let t = Technique {
id: "range-416",
name: "Range 416/404",
oracle_class: OracleClass::Existence,
vector: Vector::CacheProbing,
strength: NormativeStrength::Should,
normalization_weight: None,
inverted_signal_weight: None,
method_relevant: false,
parser_relevant: false,
applicability: always_applicable,
contradiction_surface: SignalSurface::Status,
};
let t2 = t;
assert_eq!(t2.id, "range-416");
assert_eq!(t2.strength, NormativeStrength::Should);
assert_eq!(t2.oracle_class, OracleClass::Existence);
}
#[test]
fn impact_class_copy_and_eq() {
let a = ImpactClass::High;
let b = a;
assert_eq!(a, b);
assert_ne!(a, ImpactClass::Low);
}
#[test]
fn impact_class_serialize_roundtrip() {
let json = serde_json::to_string(&ImpactClass::Medium).expect("serialization failed");
let back: ImpactClass = serde_json::from_str(&json).expect("deserialization failed");
assert_eq!(back, ImpactClass::Medium);
}
#[test]
fn scoring_dimension_copy_and_eq() {
let a = ScoringDimension::Confidence;
let b = a;
assert_eq!(a, b);
assert_ne!(a, ScoringDimension::Impact);
}
#[test]
fn scoring_reason_serialize_roundtrip() {
let reason = ScoringReason {
description: "Status differential 416 vs 404".into(),
points: 75,
dimension: ScoringDimension::Confidence,
};
let json = serde_json::to_string(&reason).expect("serialization failed");
let back: ScoringReason = serde_json::from_str(&json).expect("deserialization failed");
assert_eq!(back.description, "Status differential 416 vs 404");
assert_eq!(back.points, 75);
assert_eq!(back.dimension, ScoringDimension::Confidence);
}
#[test]
fn scoring_reason_negative_points() {
let reason = ScoringReason {
description: "Inconsistent across samples".into(),
points: -10,
dimension: ScoringDimension::Confidence,
};
let json = serde_json::to_string(&reason).expect("serialization failed");
let back: ScoringReason = serde_json::from_str(&json).expect("deserialization failed");
assert_eq!(back.points, -10);
}
#[test]
fn oracle_result_with_confidence_and_impact_serializes() {
let result = OracleResult {
class: OracleClass::Existence,
verdict: OracleVerdict::Confirmed,
severity: Some(Severity::High),
confidence: 88,
impact_class: Some(ImpactClass::High),
reasons: vec![
ScoringReason {
description: "Status differential 416 vs 404".into(),
points: 75,
dimension: ScoringDimension::Confidence,
},
ScoringReason {
description: "Content-Range reveals exact size".into(),
points: 12,
dimension: ScoringDimension::Impact,
},
],
signals: vec![],
technique_id: None,
vector: None,
normative_strength: None,
label: None,
leaks: None,
rfc_basis: None,
};
let json = serde_json::to_value(&result).expect("serialization failed");
assert_eq!(json["confidence"], 88);
assert_eq!(json["impact_class"], "High");
assert_eq!(json["reasons"].as_array().expect("expected array").len(), 2);
assert_eq!(json["reasons"][0]["points"], 75);
assert_eq!(json["reasons"][1]["dimension"], "Impact");
}
#[test]
fn oracle_class_display() {
assert_eq!(format!("{}", OracleClass::Existence), "Existence");
}
#[test]
fn oracle_verdict_display() {
assert_eq!(format!("{}", OracleVerdict::Confirmed), "Confirmed");
assert_eq!(format!("{}", OracleVerdict::Likely), "Likely");
assert_eq!(format!("{}", OracleVerdict::Inconclusive), "Inconclusive");
assert_eq!(format!("{}", OracleVerdict::NotPresent), "NotPresent");
}
#[test]
fn severity_display() {
assert_eq!(format!("{}", Severity::High), "High");
assert_eq!(format!("{}", Severity::Medium), "Medium");
assert_eq!(format!("{}", Severity::Low), "Low");
}
#[test]
fn oracle_result_zero_confidence_omits_impact_and_reasons() {
let result = OracleResult {
class: OracleClass::Existence,
verdict: OracleVerdict::NotPresent,
severity: None,
confidence: 0,
impact_class: None,
reasons: vec![],
signals: vec![],
technique_id: None,
vector: None,
normative_strength: None,
label: None,
leaks: None,
rfc_basis: None,
};
let json = serde_json::to_value(&result).expect("serialization failed");
let obj = json.as_object().expect("expected object");
assert!(!obj.contains_key("impact_class"));
assert!(!obj.contains_key("reasons"));
}
#[test]
fn header_map_serde_preserves_multi_valued_headers() {
use http::{header, HeaderMap, HeaderValue};
let mut headers = HeaderMap::new();
headers.append(header::SET_COOKIE, HeaderValue::from_static("a=1; Path=/"));
headers.append(header::SET_COOKIE, HeaderValue::from_static("b=2; Secure"));
let surface = ResponseSurface {
status: http::StatusCode::OK,
headers,
body: bytes::Bytes::new(),
timing_ns: 0,
};
let json = serde_json::to_string(&surface).expect("serialization failed");
let back: ResponseSurface = serde_json::from_str(&json).expect("deserialization failed");
let cookies: Vec<&str> = back
.headers
.get_all(header::SET_COOKIE)
.iter()
.map(|v| v.to_str().expect("valid utf8"))
.collect();
assert_eq!(
cookies.len(),
2,
"both Set-Cookie values must survive the round-trip"
);
assert!(cookies.contains(&"a=1; Path=/"));
assert!(cookies.contains(&"b=2; Secure"));
}
#[test]
fn header_map_serde_format_is_array_of_pairs() {
use http::{header, HeaderMap, HeaderValue};
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
HeaderValue::from_static("application/json"),
);
let surface = ResponseSurface {
status: http::StatusCode::OK,
headers,
body: bytes::Bytes::new(),
timing_ns: 0,
};
let json = serde_json::to_value(&surface).expect("serialization failed");
assert!(
json["headers"].is_array(),
"headers must serialize as an array of [name, value] pairs, got: {}",
json["headers"]
);
let pairs = json["headers"].as_array().expect("array");
assert_eq!(pairs.len(), 1);
assert_eq!(pairs[0][0], "content-type");
assert_eq!(pairs[0][1], "application/json");
}
#[test]
fn error_cli_variant_exists_and_displays_correctly() {
let err = Error::Cli("bad argument".to_owned());
assert_eq!(err.to_string(), "cli error: bad argument");
}
#[test]
fn error_cli_is_distinct_from_http() {
let cli = Error::Cli("oops".to_owned());
let http = Error::Http("oops".to_owned());
assert_ne!(cli.to_string(), http.to_string());
}
fn test_technique() -> Technique {
Technique {
id: "get-200-404",
name: "GET 200/404",
oracle_class: OracleClass::Existence,
vector: Vector::StatusCodeDiff,
strength: NormativeStrength::Must,
normalization_weight: Some(0.2),
inverted_signal_weight: None,
method_relevant: false,
parser_relevant: false,
applicability: always_applicable,
contradiction_surface: SignalSurface::Status,
}
}
#[test]
fn from_technique_confirmed_fields_match_inputs() {
let technique = test_technique();
let result = OracleResult::from_technique(
OracleVerdict::Confirmed,
Some(Severity::High),
"403 vs 404".into(),
SignalKind::StatusCodeDiff,
85,
&technique,
);
assert_eq!(result.class, OracleClass::Existence);
assert_eq!(result.verdict, OracleVerdict::Confirmed);
assert_eq!(result.severity, Some(Severity::High));
assert_eq!(result.confidence, 85);
assert_eq!(result.signals.len(), 1);
assert_eq!(result.signals[0].kind, SignalKind::StatusCodeDiff);
assert_eq!(result.signals[0].evidence, "403 vs 404");
assert_eq!(result.technique_id.as_deref(), Some("get-200-404"));
assert_eq!(result.vector, Some(Vector::StatusCodeDiff));
assert_eq!(result.normative_strength, Some(NormativeStrength::Must));
}
#[test]
fn from_technique_not_present_none_severity_fields_match() {
let technique = test_technique();
let result = OracleResult::from_technique(
OracleVerdict::NotPresent,
None,
"404 vs 404".into(),
SignalKind::StatusCodeDiff,
0,
&technique,
);
assert_eq!(result.verdict, OracleVerdict::NotPresent);
assert_eq!(result.severity, None);
assert_eq!(result.confidence, 0);
assert_eq!(result.signals[0].evidence, "404 vs 404");
assert_eq!(result.technique_id.as_deref(), Some("get-200-404"));
assert_eq!(result.vector, Some(Vector::StatusCodeDiff));
assert_eq!(result.normative_strength, Some(NormativeStrength::Must));
}
#[test]
fn from_technique_optional_fields_are_none_or_empty() {
let technique = test_technique();
let result = OracleResult::from_technique(
OracleVerdict::Confirmed,
Some(Severity::Medium),
"evidence".into(),
SignalKind::BodyDiff,
50,
&technique,
);
assert!(result.impact_class.is_none());
assert!(result.reasons.is_empty());
assert!(result.label.is_none());
assert!(result.leaks.is_none());
assert!(result.rfc_basis.is_none());
assert!(result.signals[0].rfc_basis.is_none());
}
fn make_result(verdict: OracleVerdict) -> OracleResult {
OracleResult {
class: OracleClass::Existence,
verdict,
severity: None,
confidence: 0,
impact_class: None,
reasons: vec![],
signals: vec![],
technique_id: None,
vector: None,
normative_strength: None,
label: None,
leaks: None,
rfc_basis: None,
}
}
#[test]
fn into_outcome_confirmed_becomes_positive() {
let outcome = make_result(OracleVerdict::Confirmed).into_outcome();
assert!(matches!(outcome, StrategyOutcome::Positive(_)));
}
#[test]
fn into_outcome_likely_becomes_no_signal() {
let outcome = make_result(OracleVerdict::Likely).into_outcome();
assert!(matches!(outcome, StrategyOutcome::NoSignal(_)));
}
#[test]
fn into_outcome_inconclusive_becomes_no_signal() {
let outcome = make_result(OracleVerdict::Inconclusive).into_outcome();
assert!(matches!(outcome, StrategyOutcome::NoSignal(_)));
}
#[test]
fn into_outcome_not_present_becomes_no_signal() {
let outcome = make_result(OracleVerdict::NotPresent).into_outcome();
assert!(matches!(outcome, StrategyOutcome::NoSignal(_)));
}