use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConnectMethodTestVerdict {
Pass,
Fail,
ExpectedFailure, Skipped,
}
impl fmt::Display for ConnectMethodTestVerdict {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Pass => write!(f, "PASS"),
Self::Fail => write!(f, "FAIL"),
Self::ExpectedFailure => write!(f, "XFAIL"),
Self::Skipped => write!(f, "SKIP"),
}
}
}
#[derive(Debug, Clone)]
pub struct ConnectMethodConformanceCase {
pub id: String,
pub description: String,
pub connect_request: ConnectRequest,
pub expected_response_status: Option<u16>,
pub should_establish_tunnel: bool,
}
#[derive(Debug, Clone)]
pub struct ConnectRequest {
pub authority: String,
pub headers: Vec<(String, String)>,
pub tunnel_test_data: Vec<u8>,
}
#[derive(Debug, Clone, Serialize)]
pub struct ConnectMethodTestResult {
pub case_id: String,
pub verdict: ConnectMethodTestVerdict,
pub error: Option<String>,
pub asupersync_response_status: Option<u16>,
pub h2_response_status: Option<u16>,
pub asupersync_tunnel_established: bool,
pub h2_tunnel_established: bool,
pub response_status_match: bool,
pub tunnel_behavior_match: bool,
pub test_duration_ms: u64,
}
#[derive(Debug, Clone, Serialize)]
pub struct ConnectMethodComplianceSummary {
pub passed: usize,
pub failed: usize,
pub expected_failures: usize,
pub skipped: usize,
pub total: usize,
pub compliance_score: f64,
}
#[derive(Debug, Clone, Serialize)]
pub struct ConnectMethodComplianceReport {
pub test_run_id: String,
pub timestamp: String,
pub total_cases: usize,
pub results: Vec<ConnectMethodTestResult>,
pub summary: ConnectMethodComplianceSummary,
}
pub struct ConnectMethodConformanceTester {
pub test_cases: Vec<ConnectMethodConformanceCase>,
}
impl Default for ConnectMethodConformanceTester {
fn default() -> Self {
Self::new()
}
}
impl ConnectMethodConformanceTester {
pub fn new() -> Self {
Self {
test_cases: Self::create_test_cases(),
}
}
fn create_test_cases() -> Vec<ConnectMethodConformanceCase> {
vec![
ConnectMethodConformanceCase {
id: "CONNECT-001".to_string(),
description: "Basic CONNECT to HTTPS endpoint".to_string(),
connect_request: ConnectRequest {
authority: "example.com:443".to_string(),
headers: vec![("User-Agent".to_string(), "test-client/1.0".to_string())],
tunnel_test_data: b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".to_vec(),
},
expected_response_status: Some(200),
should_establish_tunnel: true,
},
ConnectMethodConformanceCase {
id: "CONNECT-002".to_string(),
description: "CONNECT to non-standard port".to_string(),
connect_request: ConnectRequest {
authority: "test.example.org:8080".to_string(),
headers: vec![(
"Proxy-Authorization".to_string(),
"Basic dGVzdDp0ZXN0".to_string(),
)],
tunnel_test_data: b"PING".to_vec(),
},
expected_response_status: Some(200),
should_establish_tunnel: true,
},
ConnectMethodConformanceCase {
id: "CONNECT-003".to_string(),
description: "CONNECT with IPv4 address".to_string(),
connect_request: ConnectRequest {
authority: "192.168.1.100:80".to_string(),
headers: vec![],
tunnel_test_data: b"Hello, tunnel!".to_vec(),
},
expected_response_status: Some(200),
should_establish_tunnel: true,
},
ConnectMethodConformanceCase {
id: "CONNECT-004".to_string(),
description: "CONNECT with IPv6 address".to_string(),
connect_request: ConnectRequest {
authority: "[2001:db8::1]:443".to_string(),
headers: vec![("X-Forwarded-For".to_string(), "203.0.113.1".to_string())],
tunnel_test_data: b"IPv6 tunnel test".to_vec(),
},
expected_response_status: Some(200),
should_establish_tunnel: true,
},
ConnectMethodConformanceCase {
id: "CONNECT-005".to_string(),
description: "CONNECT to blocked/forbidden endpoint".to_string(),
connect_request: ConnectRequest {
authority: "blocked.example.com:443".to_string(),
headers: vec![],
tunnel_test_data: Vec::new(),
},
expected_response_status: Some(403),
should_establish_tunnel: false,
},
ConnectMethodConformanceCase {
id: "CONNECT-006".to_string(),
description: "CONNECT with invalid authority format".to_string(),
connect_request: ConnectRequest {
authority: "invalid-authority-no-port".to_string(),
headers: vec![],
tunnel_test_data: Vec::new(),
},
expected_response_status: Some(400),
should_establish_tunnel: false,
},
ConnectMethodConformanceCase {
id: "CONNECT-007".to_string(),
description: "CONNECT tunnel bidirectional data flow".to_string(),
connect_request: ConnectRequest {
authority: "echo.example.com:9090".to_string(),
headers: vec![("X-Protocol".to_string(), "websocket".to_string())],
tunnel_test_data: b"ECHO_REQUEST\nHello, bidirectional tunnel!\n".to_vec(),
},
expected_response_status: Some(200),
should_establish_tunnel: true,
},
ConnectMethodConformanceCase {
id: "CONNECT-008".to_string(),
description: "CONNECT with large authority string".to_string(),
connect_request: ConnectRequest {
authority: format!("{}:443", "a".repeat(253)), headers: vec![],
tunnel_test_data: b"Large authority test".to_vec(),
},
expected_response_status: Some(200),
should_establish_tunnel: true,
},
ConnectMethodConformanceCase {
id: "CONNECT-009".to_string(),
description: "CONNECT with timeout scenario".to_string(),
connect_request: ConnectRequest {
authority: "slow.example.com:443".to_string(),
headers: vec![("X-Timeout".to_string(), "30".to_string())],
tunnel_test_data: b"Timeout test".to_vec(),
},
expected_response_status: Some(200),
should_establish_tunnel: true,
},
ConnectMethodConformanceCase {
id: "CONNECT-010".to_string(),
description: "CONNECT tunnel termination handling".to_string(),
connect_request: ConnectRequest {
authority: "term.example.com:443".to_string(),
headers: vec![],
tunnel_test_data: b"CLOSE_TUNNEL\n".to_vec(),
},
expected_response_status: Some(200),
should_establish_tunnel: true,
},
]
}
pub async fn run_all_tests(&mut self) -> ConnectMethodComplianceReport {
let test_run_id = uuid::Uuid::new_v4().to_string();
let timestamp = chrono::Utc::now().to_rfc3339();
let total_cases = self.test_cases.len();
let mut results = Vec::new();
for test_case in &self.test_cases {
let result = self.run_single_test(test_case).await;
results.push(result);
}
let summary = self.compute_summary(&results);
ConnectMethodComplianceReport {
test_run_id,
timestamp,
total_cases,
results,
summary,
}
}
async fn run_single_test(
&self,
test_case: &ConnectMethodConformanceCase,
) -> ConnectMethodTestResult {
let start_time = std::time::Instant::now();
let asupersync_result = self.test_connect_with_asupersync(test_case).await;
let h2_result = self.test_connect_with_h2(test_case).await;
let duration = start_time.elapsed();
let test_duration_ms = duration.as_millis() as u64;
if let Some(error) = backend_unwired_error(&asupersync_result, &h2_result) {
return Self::skipped_backend_result(test_case, error, test_duration_ms);
}
match (asupersync_result, h2_result) {
(Ok((asupersync_status, asupersync_tunnel)), Ok((h2_status, h2_tunnel))) => {
let response_status_match = asupersync_status == h2_status;
let tunnel_behavior_match = asupersync_tunnel == h2_tunnel;
let verdict = if response_status_match && tunnel_behavior_match {
let status_correct = test_case
.expected_response_status
.is_none_or(|expected| asupersync_status == expected);
let tunnel_correct = asupersync_tunnel == test_case.should_establish_tunnel;
if status_correct && tunnel_correct {
ConnectMethodTestVerdict::Pass
} else {
ConnectMethodTestVerdict::Fail
}
} else {
ConnectMethodTestVerdict::Fail
};
ConnectMethodTestResult {
case_id: test_case.id.clone(),
verdict,
error: None,
asupersync_response_status: Some(asupersync_status),
h2_response_status: Some(h2_status),
asupersync_tunnel_established: asupersync_tunnel,
h2_tunnel_established: h2_tunnel,
response_status_match,
tunnel_behavior_match,
test_duration_ms,
}
}
(Err(e), _) | (_, Err(e)) => ConnectMethodTestResult {
case_id: test_case.id.clone(),
verdict: ConnectMethodTestVerdict::Fail,
error: Some(e),
asupersync_response_status: None,
h2_response_status: None,
asupersync_tunnel_established: false,
h2_tunnel_established: false,
response_status_match: false,
tunnel_behavior_match: false,
test_duration_ms,
},
}
}
fn skipped_backend_result(
test_case: &ConnectMethodConformanceCase,
error: String,
test_duration_ms: u64,
) -> ConnectMethodTestResult {
ConnectMethodTestResult {
case_id: test_case.id.clone(),
verdict: ConnectMethodTestVerdict::Skipped,
error: Some(error),
asupersync_response_status: None,
h2_response_status: None,
asupersync_tunnel_established: false,
h2_tunnel_established: false,
response_status_match: false,
tunnel_behavior_match: false,
test_duration_ms,
}
}
async fn test_connect_with_asupersync(
&self,
test_case: &ConnectMethodConformanceCase,
) -> Result<(u16, bool), String> {
Err(format!(
"asupersync HTTP/2 CONNECT backend not wired; refusing to synthesize comparison result for {}",
test_case.id
))
}
async fn test_connect_with_h2(
&self,
test_case: &ConnectMethodConformanceCase,
) -> Result<(u16, bool), String> {
Err(format!(
"h2 HTTP/2 CONNECT backend not wired; refusing to synthesize comparison result for {}",
test_case.id
))
}
fn compute_summary(
&self,
results: &[ConnectMethodTestResult],
) -> ConnectMethodComplianceSummary {
let passed = results
.iter()
.filter(|r| r.verdict == ConnectMethodTestVerdict::Pass)
.count();
let failed = results
.iter()
.filter(|r| r.verdict == ConnectMethodTestVerdict::Fail)
.count();
let expected_failures = results
.iter()
.filter(|r| r.verdict == ConnectMethodTestVerdict::ExpectedFailure)
.count();
let skipped = results
.iter()
.filter(|r| r.verdict == ConnectMethodTestVerdict::Skipped)
.count();
let total = results.len();
let compliance_score = if total > 0 {
(passed + expected_failures) as f64 / total as f64
} else {
0.0
};
ConnectMethodComplianceSummary {
passed,
failed,
expected_failures,
skipped,
total,
compliance_score,
}
}
pub fn generate_markdown_report(&self, report: &ConnectMethodComplianceReport) -> String {
let mut md = String::new();
md.push_str("# HTTP/2 CONNECT Method Handling Conformance Report\n\n");
md.push_str(&format!("**Test Run ID:** {}\n", report.test_run_id));
md.push_str(&format!("**Timestamp:** {}\n", report.timestamp));
md.push_str(&format!("**Total Test Cases:** {}\n\n", report.total_cases));
md.push_str("## Summary\n\n");
md.push_str(&format!("- โ
**Passed:** {}\n", report.summary.passed));
md.push_str(&format!("- โ **Failed:** {}\n", report.summary.failed));
md.push_str(&format!(
"- โ ๏ธ **Expected Failures:** {}\n",
report.summary.expected_failures
));
md.push_str(&format!("- โญ๏ธ **Skipped:** {}\n", report.summary.skipped));
md.push_str(&format!(
"- ๐ฏ **Compliance Score:** {:.1}%\n\n",
report.summary.compliance_score * 100.0
));
md.push_str("## Test Results\n\n");
md.push_str(
"| Test ID | Description | Verdict | Asupersync Status | H2 Status | Tunnel Match |\n",
);
md.push_str(
"|---------|-------------|---------|-------------------|-----------|-------------|\n",
);
for result in &report.results {
let tunnel_icon = if result.tunnel_behavior_match {
"โ
"
} else {
"โ"
};
md.push_str(&format!(
"| {} | {} | {} | {} | {} | {} |\n",
result.case_id,
self.test_cases
.iter()
.find(|case| case.id == result.case_id)
.map(|case| case.description.as_str())
.unwrap_or("Unknown"),
result.verdict,
result
.asupersync_response_status
.map(|s| s.to_string())
.unwrap_or_else(|| "Error".to_string()),
result
.h2_response_status
.map(|s| s.to_string())
.unwrap_or_else(|| "Error".to_string()),
tunnel_icon
));
}
md.push_str("\n## Failed Tests\n\n");
let failed_tests: Vec<_> = report
.results
.iter()
.filter(|r| r.verdict == ConnectMethodTestVerdict::Fail)
.collect();
if failed_tests.is_empty() {
md.push_str("No tests failed.\n\n");
} else {
for result in failed_tests {
md.push_str(&format!("### {}\n\n", result.case_id));
if let Some(error) = &result.error {
md.push_str(&format!("**Error:** {}\n\n", error));
}
md.push_str(&format!(
"**Response Status:** asupersync={:?}, h2={:?}\n",
result.asupersync_response_status, result.h2_response_status
));
md.push_str(&format!(
"**Tunnel Established:** asupersync={}, h2={}\n\n",
result.asupersync_tunnel_established, result.h2_tunnel_established
));
}
}
md.push_str("\n## Skipped Tests\n\n");
let skipped_tests: Vec<_> = report
.results
.iter()
.filter(|r| r.verdict == ConnectMethodTestVerdict::Skipped)
.collect();
if skipped_tests.is_empty() {
md.push_str("No tests were skipped.\n\n");
} else {
for result in skipped_tests {
md.push_str(&format!("### {}\n\n", result.case_id));
if let Some(error) = &result.error {
md.push_str(&format!("**Reason:** {}\n\n", error));
}
}
}
md.push_str("---\n");
md.push_str(&format!(
"*Generated by asupersync conformance tester at {}*\n",
chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC")
));
md
}
}
fn backend_unwired_error(
asupersync_result: &Result<(u16, bool), String>,
h2_result: &Result<(u16, bool), String>,
) -> Option<String> {
let mut errors = Vec::new();
if let Err(error) = asupersync_result
&& is_backend_unwired(error)
{
errors.push(error.as_str());
}
if let Err(error) = h2_result
&& is_backend_unwired(error)
{
errors.push(error.as_str());
}
if errors.is_empty() {
None
} else {
Some(errors.join("; "))
}
}
fn is_backend_unwired(error: &str) -> bool {
error.contains("backend not wired") && error.contains("refusing to synthesize")
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn connect_harness_does_not_report_synthetic_passes() {
let mut tester = ConnectMethodConformanceTester::new();
let total_cases = tester.test_cases.len();
let report = tester.run_all_tests().await;
assert_eq!(report.summary.total, total_cases);
assert_eq!(report.summary.passed, 0);
assert_eq!(report.summary.failed, 0);
assert_eq!(report.summary.expected_failures, 0);
assert_eq!(report.summary.skipped, total_cases);
assert_eq!(report.summary.compliance_score, 0.0);
assert!(
report
.results
.iter()
.all(|result| result.verdict == ConnectMethodTestVerdict::Skipped)
);
assert!(
report
.results
.iter()
.all(|result| { result.error.as_deref().is_some_and(is_backend_unwired) })
);
}
#[tokio::test]
async fn markdown_report_explains_skipped_unwired_backends() {
let mut tester = ConnectMethodConformanceTester::new();
let report = tester.run_all_tests().await;
let markdown = tester.generate_markdown_report(&report);
assert!(markdown.contains("## Skipped Tests"));
assert!(markdown.contains("asupersync HTTP/2 CONNECT backend not wired"));
assert!(markdown.contains("h2 HTTP/2 CONNECT backend not wired"));
assert!(markdown.contains("refusing to synthesize comparison result"));
}
}