#![cfg(test)]
#[derive(Debug, Clone)]
struct UpgradeResponseFixture {
status: u16,
headers: Vec<(String, String)>,
body: Vec<u8>,
}
impl UpgradeResponseFixture {
fn new_upgrade_required_tls() -> Self {
Self {
status: 426,
headers: vec![
("upgrade".to_string(), "TLS/1.2".to_string()),
("connection".to_string(), "Upgrade".to_string()),
("content-type".to_string(), "text/plain".to_string()),
],
body: b"TLS required for OTLP endpoint".to_vec(),
}
}
fn new_upgrade_required_h2() -> Self {
Self {
status: 426,
headers: vec![
("upgrade".to_string(), "h2".to_string()),
("connection".to_string(), "Upgrade".to_string()),
("content-type".to_string(), "text/plain".to_string()),
],
body: b"HTTP/2 required for OTLP endpoint".to_vec(),
}
}
fn new_upgrade_required_h3() -> Self {
Self {
status: 426,
headers: vec![
("upgrade".to_string(), "h3".to_string()),
("connection".to_string(), "Upgrade".to_string()),
("alt-svc".to_string(), "h3=\":443\"".to_string()),
],
body: b"HTTP/3 required for OTLP endpoint".to_vec(),
}
}
fn new_upgrade_required_no_header() -> Self {
Self {
status: 426,
headers: vec![("content-type".to_string(), "text/plain".to_string())],
body: b"Upgrade Required".to_vec(),
}
}
}
#[derive(Debug)]
struct UpgradeHandlerFixture {
responses_received: Vec<UpgradeResponseFixture>,
error_messages: Vec<String>,
}
impl UpgradeHandlerFixture {
fn new() -> Self {
Self {
responses_received: Vec::new(),
error_messages: Vec::new(),
}
}
fn handle_response_defective(
&mut self,
response: UpgradeResponseFixture,
) -> Result<(), String> {
self.responses_received.push(response.clone());
match response.status {
200..=299 => Ok(()),
400..=499 => {
let error = format!("OTLP client error: {} - batch dropped", response.status);
self.error_messages.push(error.clone());
Err(error)
}
_ => {
let error = format!("Unexpected status: {}", response.status);
self.error_messages.push(error.clone());
Err(error)
}
}
}
fn handle_response_correct(&mut self, response: UpgradeResponseFixture) -> Result<(), String> {
self.responses_received.push(response.clone());
match response.status {
200..=299 => Ok(()),
426 => {
let upgrade_header = response
.headers
.iter()
.find(|(name, _)| name.eq_ignore_ascii_case("upgrade"))
.map(|(_, value)| value.clone());
let error = if let Some(required_protocol) = upgrade_header {
format!(
"OTLP endpoint requires protocol upgrade to {} (RFC 9110 ยง15.5.22). \
Reconfigure client with required protocol or use TLS endpoint.",
required_protocol
)
} else {
"OTLP endpoint requires protocol upgrade (RFC 9110 ยง15.5.22). \
Check server documentation for required protocol."
.to_string()
};
self.error_messages.push(error.clone());
Err(error)
}
400..=499 => {
let error = format!("OTLP client error: {} - batch dropped", response.status);
self.error_messages.push(error.clone());
Err(error)
}
_ => {
let error = format!("Unexpected status: {}", response.status);
self.error_messages.push(error.clone());
Err(error)
}
}
}
}
#[test]
fn audit_upgrade_required_tls_scenario() {
println!("๐ AUDIT: OTLP 426 Upgrade Required (TLS) response handling");
println!("๐ RFC 9110 ยง15.5.22 requirements:");
println!(" โข 426 indicates server requires protocol upgrade");
println!(" โข Client should inspect Upgrade header for required protocol");
println!(" โข Should fail-fast with actionable upgrade guidance");
println!(" โข NOT: retry without upgrade (RFC violation)");
println!(" โข NOT: treat as generic 4xx (user confusion)");
let tls_upgrade_response = UpgradeResponseFixture::new_upgrade_required_tls();
println!("๐ Test scenario:");
println!(" Status: 426 Upgrade Required");
println!(" Upgrade header: TLS/1.2");
println!(" Expected: Fail-fast with TLS upgrade guidance");
println!("๐ Testing defective implementation (current behavior):");
let mut defective_handler = UpgradeHandlerFixture::new();
let defective_result =
defective_handler.handle_response_defective(tls_upgrade_response.clone());
println!(" Result: {:?}", defective_result);
println!(
" Error message: {:?}",
defective_handler.error_messages.last()
);
assert!(defective_result.is_err());
let error_msg = defective_handler.error_messages.last().unwrap();
assert!(error_msg.contains("OTLP client error: 426 - batch dropped"));
assert!(!error_msg.contains("upgrade"));
assert!(!error_msg.contains("TLS"));
println!("โ ๏ธ DEFECTIVE: Generic 4xx error message, no upgrade guidance");
println!("๐ Testing correct implementation (RFC 9110 compliant):");
let mut correct_handler = UpgradeHandlerFixture::new();
let correct_result = correct_handler.handle_response_correct(tls_upgrade_response);
println!(" Result: {:?}", correct_result);
println!(
" Error message: {:?}",
correct_handler.error_messages.last()
);
assert!(correct_result.is_err());
let correct_error = correct_handler.error_messages.last().unwrap();
assert!(correct_error.contains("protocol upgrade to TLS/1.2"));
assert!(correct_error.contains("RFC 9110"));
assert!(correct_error.contains("Reconfigure client"));
println!("โ
CORRECT: Specific upgrade guidance with actionable advice");
println!("๐จ AUDIT FINDING: DEFECTIVE");
println!(" Current: 426 โ generic 4xx client error");
println!(" Required: 426 โ specific upgrade guidance per RFC 9110");
}
#[test]
fn audit_upgrade_required_h2_scenario() {
println!("๐ AUDIT: OTLP 426 Upgrade Required (HTTP/2) response handling");
let h2_upgrade_response = UpgradeResponseFixture::new_upgrade_required_h2();
println!("๐ HTTP/2 upgrade scenario:");
println!(" Status: 426 Upgrade Required");
println!(" Upgrade header: h2");
println!(" Expected: HTTP/2 specific upgrade guidance");
let mut defective_handler = UpgradeHandlerFixture::new();
let mut correct_handler = UpgradeHandlerFixture::new();
let _defective_result =
defective_handler.handle_response_defective(h2_upgrade_response.clone());
let _correct_result = correct_handler.handle_response_correct(h2_upgrade_response);
println!("๐ Defective behavior:");
println!(" Error: {:?}", defective_handler.error_messages.last());
println!("๐ Correct behavior:");
println!(" Error: {:?}", correct_handler.error_messages.last());
let correct_error = correct_handler.error_messages.last().unwrap();
assert!(correct_error.contains("upgrade to h2"));
assert!(
!defective_handler
.error_messages
.last()
.unwrap()
.contains("h2")
);
println!("โ
CORRECT: Protocol-specific guidance (h2)");
println!("โ ๏ธ DEFECTIVE: Generic error loses upgrade protocol context");
}
#[test]
fn audit_upgrade_required_h3_scenario() {
println!("๐ AUDIT: OTLP 426 Upgrade Required (HTTP/3) response handling");
let h3_upgrade_response = UpgradeResponseFixture::new_upgrade_required_h3();
assert!(
String::from_utf8_lossy(&h3_upgrade_response.body).contains("HTTP/3"),
"HTTP/3 upgrade fixture body should describe the requested protocol"
);
assert!(
h3_upgrade_response
.headers
.iter()
.any(|(name, value)| name == "alt-svc" && value.contains("h3")),
"HTTP/3 upgrade responses should carry an h3 Alt-Svc hint"
);
let mut correct_handler = UpgradeHandlerFixture::new();
let correct_result = correct_handler.handle_response_correct(h3_upgrade_response);
assert!(correct_result.is_err());
let correct_error = correct_handler.error_messages.last().unwrap();
assert!(
correct_error.contains("upgrade to h3"),
"HTTP/3 426 handling should preserve the required protocol: {correct_error}"
);
assert!(
correct_error.contains("RFC 9110"),
"HTTP/3 426 handling should keep the standards reference: {correct_error}"
);
}
#[test]
fn audit_upgrade_required_no_header_scenario() {
println!("๐ AUDIT: OTLP 426 Upgrade Required without Upgrade header");
let no_header_response = UpgradeResponseFixture::new_upgrade_required_no_header();
println!("๐ Malformed 426 scenario:");
println!(" Status: 426 Upgrade Required");
println!(" Missing: Upgrade header (RFC 9110 violation by server)");
println!(" Expected: Fallback guidance with RFC reference");
let mut correct_handler = UpgradeHandlerFixture::new();
let _correct_result = correct_handler.handle_response_correct(no_header_response);
println!("๐ Correct fallback behavior:");
println!(" Error: {:?}", correct_handler.error_messages.last());
let error_msg = correct_handler.error_messages.last().unwrap();
assert!(error_msg.contains("protocol upgrade"));
assert!(error_msg.contains("RFC 9110"));
assert!(error_msg.contains("server documentation"));
println!("โ
CORRECT: Fallback guidance references RFC 9110 for context");
}
#[test]
fn audit_current_status_code_classification() {
println!("๐ AUDIT: Current OTLP status code classification for 4xx range");
println!("๐ Current classification (lines 1100-1105 in otel.rs):");
println!(" 415: Special handling (compression fallback)");
println!(" 400-414, 416-499: Generic 'OTLP client error: N - batch dropped'");
println!(" Problem: 426 Upgrade Required is protocol-specific, not generic");
fn classify_client_error(status: u16) -> &'static str {
match status {
415 => "compression_fallback",
400..=499 => "non_retryable_generic_client_error", _ => "other",
}
}
println!("๐ Current 4xx error classification:");
let statuses = [400, 401, 403, 404, 415, 426, 429, 499];
for status in statuses {
let classification = classify_client_error(status);
println!(" {}: {}", status, classification);
}
println!("๐ Correct RFC-aware classification should be:");
println!(" 400: generic_client_error (Bad Request)");
println!(" 401: auth_error (Unauthorized)");
println!(" 403: auth_error (Forbidden)");
println!(" 404: config_error (Not Found - wrong endpoint)");
println!(" 415: compression_fallback (Unsupported Media Type)");
println!(" 426: upgrade_required (Protocol upgrade needed)");
println!(" 429: rate_limited (Too Many Requests)");
assert_eq!(
classify_client_error(426),
"non_retryable_generic_client_error"
);
assert_eq!(classify_client_error(415), "compression_fallback");
println!("๐จ DEFECT CONFIRMED: 426 lacks special handling like 415");
println!(" Should provide protocol upgrade guidance per RFC 9110");
}
#[test]
fn audit_rfc_9110_compliance_requirements() {
println!("๐ AUDIT: RFC 9110 ยง15.5.22 compliance for 426 Upgrade Required");
println!("๐ RFC 9110 ยง15.5.22 verbatim requirements:");
println!(" 'The 426 (Upgrade Required) status code indicates that the server");
println!(" refuses to perform the request using the current protocol but");
println!(" might be willing to do so after the client upgrades to a");
println!(" different protocol.'");
println!();
println!(" 'A server MUST send an Upgrade header field in a 426 response");
println!(" to indicate the required protocol(s).'");
println!();
println!(" 'A client MAY repeat a request if it adds a suitable Upgrade");
println!(" header field or change the request to a different protocol.'");
println!("๐ RFC compliance analysis:");
println!(" โ
Server requirement: Send Upgrade header (server's responsibility)");
println!(" โ Client requirement: Handle upgrade guidance (our responsibility)");
println!(" โ User experience: Provide actionable next steps (our responsibility)");
println!("๐ Current implementation vs RFC guidance:");
let current_message = "OTLP client error: 426 - batch dropped";
let rfc_compliant_message = "OTLP endpoint requires protocol upgrade to TLS/1.2 (RFC 9110 ยง15.5.22). Reconfigure client with required protocol or use TLS endpoint.";
println!(" Current: '{}'", current_message);
println!(" RFC-compliant: '{}'", rfc_compliant_message);
println!("๐ RFC compliance gap analysis:");
println!(" Missing: Protocol identification from Upgrade header");
println!(" Missing: Actionable reconfiguration guidance");
println!(" Missing: RFC 9110 reference for context");
println!(" Missing: Distinction from other 4xx errors");
assert!(!current_message.contains("upgrade"));
assert!(!current_message.contains("protocol"));
assert!(!current_message.contains("RFC"));
assert!(current_message.contains("batch dropped"));
assert!(rfc_compliant_message.contains("protocol upgrade"));
assert!(rfc_compliant_message.contains("RFC 9110"));
assert!(rfc_compliant_message.contains("Reconfigure"));
println!("๐จ RFC 9110 COMPLIANCE GAP: Lacks upgrade-specific handling");
println!(" Required: Extract Upgrade header, provide reconfiguration guidance");
}