use super::classify;
use http::StatusCode;
use parlov_core::{ImpactClass, OracleVerdict, Severity, Signal, SignalKind};
use crate::signals::tests::status_code_diff_technique;
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 technique = status_code_diff_technique();
let r = classify(b, p, vec![], &technique);
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"
);
}
#[test]
fn forbidden_vs_not_found() {
assert_pattern(
403,
404,
OracleVerdict::Confirmed,
Some(Severity::Medium),
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::Medium),
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::Medium),
Some("Authentication-based differential"),
true,
Some("RFC 9110 §15.5.2"),
);
}
#[test]
fn conflict_vs_created() {
assert_pattern(
409,
201,
OracleVerdict::Confirmed,
Some(Severity::Medium),
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::Medium),
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::Medium),
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::Medium),
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::Medium),
Some("Validation-path differential"),
true,
Some("RFC 9110 §15.5.21"),
);
}
#[test]
fn unprocessable_vs_created() {
assert_pattern(
422,
201,
OracleVerdict::Confirmed,
Some(Severity::Medium),
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::Medium),
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::Medium),
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::Medium),
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::Medium),
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::Medium),
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::Medium),
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::Medium),
Some("Conflict-based differential"),
true,
Some("RFC 9110 §15.5.10"),
);
}
#[test]
fn gone_vs_not_found() {
assert_pattern(
410,
404,
OracleVerdict::Confirmed,
Some(Severity::Low),
Some("Tombstone differential"),
true,
Some("RFC 9110 §15.5.11"),
);
}
#[test]
fn moved_permanently_vs_not_found() {
assert_pattern(
301,
404,
OracleVerdict::Confirmed,
Some(Severity::Low),
Some("Redirect-based differential"),
true,
Some("RFC 9110 §15.4.2"),
);
}
#[test]
fn payload_too_large_vs_not_found() {
assert_pattern(
413,
404,
OracleVerdict::Confirmed,
Some(Severity::Low),
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::Low),
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::Low),
Some("Async-acceptance differential"),
true,
Some("RFC 9110 §15.3.3"),
);
}
#[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 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 payment_required_vs_not_found() {
assert_pattern(
402,
404,
OracleVerdict::Likely,
Some(Severity::Low),
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::Low),
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::Low),
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::Low),
Some("Rate-limit-based differential"),
true,
Some("RFC 6585 §4"),
);
}
#[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);
}
#[test]
fn unrecognised_diff_not_present() {
assert_pattern(418, 404, OracleVerdict::NotPresent, None, None, false, None);
}
#[test]
fn unrecognised_diff_503_vs_200() {
assert_pattern(503, 200, OracleVerdict::NotPresent, None, None, false, None);
}
#[test]
fn classify_populates_confidence() {
let b = StatusCode::from_u16(403).expect("valid status");
let p = StatusCode::from_u16(404).expect("valid status");
let technique = status_code_diff_technique();
let r = classify(b, p, vec![], &technique);
assert!(r.confidence >= 80);
assert!(r.impact_class.is_some());
assert!(!r.reasons.is_empty());
}
#[test]
fn classify_populates_technique_metadata() {
let b = StatusCode::from_u16(403).expect("valid status");
let p = StatusCode::from_u16(404).expect("valid status");
let technique = status_code_diff_technique();
let r = classify(b, p, vec![], &technique);
assert_eq!(r.technique_id.as_deref(), Some("test-status-diff"));
assert!(r.vector.is_some());
assert!(r.normative_strength.is_some());
}
#[test]
fn may_strength_reduces_signal_confidence() {
use parlov_core::{NormativeStrength, OracleClass, Technique, Vector};
let b = StatusCode::from_u16(403).expect("valid status");
let p = StatusCode::from_u16(404).expect("valid status");
let technique = Technique {
id: "test-may",
name: "May-level test",
oracle_class: OracleClass::Existence,
vector: Vector::StatusCodeDiff,
strength: NormativeStrength::May,
};
let r = classify(b, p, vec![], &technique);
assert_eq!(r.verdict, OracleVerdict::Confirmed);
}
#[test]
fn signals_can_push_likely_to_confirmed() {
let b = StatusCode::from_u16(410).expect("valid status");
let p = StatusCode::from_u16(404).expect("valid status");
let technique = status_code_diff_technique();
let r = classify(b, p, vec![], &technique);
assert_eq!(r.verdict, OracleVerdict::Confirmed);
let signals = vec![Signal {
kind: SignalKind::HeaderPresence,
evidence: "etag present in baseline, absent in probe".into(),
rfc_basis: None,
}];
let r2 = classify(b, p, signals, &technique);
assert_eq!(r2.verdict, OracleVerdict::Confirmed);
assert!(r2.confidence > r.confidence);
}
#[test]
fn content_range_size_leak_produces_high_impact() {
let b = StatusCode::from_u16(206).expect("valid status");
let p = StatusCode::from_u16(404).expect("valid status");
let technique = status_code_diff_technique();
let signals = vec![Signal {
kind: SignalKind::MetadataLeak,
evidence: "Content-Range leaks total resource size: 1024 bytes".into(),
rfc_basis: Some("RFC 9110 §14.4".into()),
}];
let r = classify(b, p, signals, &technique);
assert_eq!(r.impact_class, Some(ImpactClass::High));
assert_eq!(r.severity, Some(Severity::High));
}
#[test]
fn etag_metadata_produces_medium_impact() {
let b = StatusCode::from_u16(200).expect("valid status");
let p = StatusCode::from_u16(404).expect("valid status");
let technique = status_code_diff_technique();
let signals = vec![Signal {
kind: SignalKind::MetadataLeak,
evidence: "ETag value \"v2\" leaks resource version identifier".into(),
rfc_basis: None,
}];
let r = classify(b, p, signals, &technique);
assert_eq!(r.impact_class, Some(ImpactClass::Medium));
}
#[test]
fn multiple_signal_families_add_corroboration() {
let b = StatusCode::from_u16(206).expect("valid status");
let p = StatusCode::from_u16(404).expect("valid status");
let technique = status_code_diff_technique();
let single = vec![Signal {
kind: SignalKind::HeaderPresence,
evidence: "content-range present in baseline".into(),
rfc_basis: None,
}];
let r1 = classify(b, p, single, &technique);
let multi = vec![
Signal {
kind: SignalKind::HeaderPresence,
evidence: "content-range present in baseline".into(),
rfc_basis: None,
},
Signal {
kind: SignalKind::HeaderPresence,
evidence: "etag present in baseline".into(),
rfc_basis: None,
},
];
let r2 = classify(b, p, multi, &technique);
assert!(r2.confidence >= r1.confidence);
}