use zenith_api::normalize::{
normalize_request, normalize_request_with_config, NormalizeConfig,
};
use zenith_api::{CanonicalRequest, Method, Protocol, Transport};
fn assert_canonical_equal(a: &CanonicalRequest, b: &CanonicalRequest) {
assert_eq!(a.method, b.method, "method mismatch");
assert_eq!(a.scheme_str(), b.scheme_str(), "scheme mismatch");
assert_eq!(
a.authority_str(),
b.authority_str(),
"authority mismatch"
);
assert_eq!(a.path_str(), b.path_str(), "path mismatch");
assert_eq!(a.query_str(), b.query_str(), "query mismatch");
assert_eq!(
a.header_count(),
b.header_count(),
"header count mismatch"
);
for hdr in a.headers_iter() {
if let Some(b_hdr) = b.find_header(hdr.name_str()) {
assert_eq!(
hdr.value_str(),
b_hdr.value_str(),
"header '{}' value mismatch",
hdr.name_str()
);
} else {
panic!("header '{}' not found in second request", hdr.name_str());
}
}
}
#[test]
fn test_simple_get_request_consistency() {
let http1_headers = [("Accept", "text/html")];
let req1 = normalize_request(
Method::Get,
"https",
"example.com",
"/",
"",
&http1_headers,
Protocol::Http1,
Transport::Tls13,
)
.unwrap();
let http2_headers = [("accept", "text/html")];
let req2 = normalize_request(
Method::Get,
"https",
"example.com",
"/",
"",
&http2_headers,
Protocol::Http2,
Transport::Tls13,
)
.unwrap();
assert_canonical_equal(&req1, &req2);
}
#[test]
fn test_post_request_with_body_consistency() {
let headers_v1 = [
("Content-Type", "application/json"),
("Accept", "application/json"),
];
let req1 = normalize_request(
Method::Post,
"https",
"api.example.com",
"/api/data",
"",
&headers_v1,
Protocol::Http1,
Transport::Tls13,
)
.unwrap();
let headers_v2 = [
("content-type", "application/json"),
("accept", "application/json"),
];
let req2 = normalize_request(
Method::Post,
"https",
"api.example.com",
"/api/data",
"",
&headers_v2,
Protocol::Http2,
Transport::Tls13,
)
.unwrap();
assert_canonical_equal(&req1, &req2);
}
#[test]
fn test_query_string_consistency() {
let headers_v1 = [];
let req1 = normalize_request(
Method::Get,
"http",
"example.com:80",
"/search",
"q=hello&lang=en",
&headers_v1,
Protocol::Http1,
Transport::Plaintext,
)
.unwrap();
let headers_v3 = [];
let req2 = normalize_request(
Method::Get,
"http",
"example.com",
"/search",
"q=hello&lang=en",
&headers_v3,
Protocol::Http3,
Transport::Tls13,
)
.unwrap();
assert_canonical_equal(&req1, &req2);
}
#[test]
fn test_header_case_insensitivity() {
let headers_lower = [("content-type", "text/plain")];
let headers_mixed = [("Content-Type", "text/plain")];
let headers_upper = [("CONTENT-TYPE", "text/plain")];
let req_lower = normalize_request(
Method::Get,
"https",
"example.com",
"/",
"",
&headers_lower,
Protocol::Http1,
Transport::Tls13,
)
.unwrap();
let req_mixed = normalize_request(
Method::Get,
"https",
"example.com",
"/",
"",
&headers_mixed,
Protocol::Http1,
Transport::Tls13,
)
.unwrap();
let req_upper = normalize_request(
Method::Get,
"https",
"example.com",
"/",
"",
&headers_upper,
Protocol::Http1,
Transport::Tls13,
)
.unwrap();
assert_canonical_equal(&req_lower, &req_mixed);
assert_canonical_equal(&req_lower, &req_upper);
}
#[test]
fn test_duplicate_header_merging() {
let headers_dup = [("Accept", "text/html"), ("Accept", "application/json")];
let req1 = normalize_request(
Method::Get,
"https",
"example.com",
"/",
"",
&headers_dup,
Protocol::Http1,
Transport::Tls13,
)
.unwrap();
let headers_h2 = [("accept", "text/html, application/json")];
let req2 = normalize_request(
Method::Get,
"https",
"example.com",
"/",
"",
&headers_h2,
Protocol::Http2,
Transport::Tls13,
)
.unwrap();
let accept1 = req1.find_header("accept").unwrap();
let accept2 = req2.find_header("accept").unwrap();
assert!(
accept1.value_str().contains("text/html"),
"req1 missing text/html"
);
assert!(
accept1.value_str().contains("application/json"),
"req1 missing application/json"
);
assert!(
accept2.value_str().contains("text/html"),
"req2 missing text/html"
);
assert!(
accept2.value_str().contains("application/json"),
"req2 missing application/json"
);
}
#[test]
fn test_path_normalization_consistency() {
let headers = [("Host", "example.com")];
let req1 = normalize_request(
Method::Get,
"https",
"example.com",
"//api//v1///test",
"",
&headers,
Protocol::Http1,
Transport::Tls13,
)
.unwrap();
assert_eq!(req1.path_str(), "/api/v1/test");
let req2 = normalize_request(
Method::Get,
"https",
"example.com",
"/api/./v1/../v1/test",
"",
&headers,
Protocol::Http1,
Transport::Tls13,
)
.unwrap();
assert_eq!(req2.path_str(), "/api/v1/test");
}
#[test]
fn test_authority_normalization_consistency() {
let headers = [("Host", "Example.COM:443")];
let req1 = normalize_request(
Method::Get,
"https",
"Example.COM:443",
"/",
"",
&headers,
Protocol::Http1,
Transport::Tls13,
)
.unwrap();
let headers_h2 = [(":authority", "example.com")];
let req2 = normalize_request(
Method::Get,
"https",
"example.com",
"/",
"",
&headers_h2,
Protocol::Http2,
Transport::Tls13,
)
.unwrap();
assert_eq!(req1.authority_str(), "example.com");
assert_eq!(req2.authority_str(), "example.com");
}
#[test]
fn test_invalid_header_name_rejection() {
let headers = [("Invalid\x00Header", "value"), ("Valid", "ok")];
let result = normalize_request(
Method::Get,
"https",
"example.com",
"/",
"",
&headers,
Protocol::Http1,
Transport::Tls13,
);
let err = result.expect_err("invalid header name should be rejected");
assert_eq!(err.message, "invalid header name");
}
#[test]
fn test_header_limit_enforcement() {
let headers_small = [("h1", "v1"), ("h2", "v2")];
let req = normalize_request(
Method::Get,
"https",
"example.com",
"/",
"",
&headers_small,
Protocol::Http1,
Transport::Tls13,
)
.unwrap();
assert_eq!(req.header_count(), 2);
}
#[test]
fn test_custom_config_consistency() {
let config = NormalizeConfig {
decode_path: true,
merge_headers: true,
normalize_case: true,
};
let headers = [("Content-Type", "application/json")];
let req1 = normalize_request_with_config(
Method::Post,
"HTTPS",
"Example.COM:443",
"/Api/Test",
"key=value",
&headers,
Protocol::Http2,
Transport::Tls13,
config,
)
.unwrap();
assert_eq!(req1.scheme_str(), "https");
assert_eq!(req1.authority_str(), "example.com");
assert_eq!(req1.path_str(), "/Api/Test");
assert_eq!(req1.query_str(), "key=value");
assert_eq!(
req1.find_header("content-type").unwrap().value_str(),
"application/json"
);
let req2 = normalize_request(
Method::Post,
"HTTPS",
"Example.COM:443",
"/Api/Test",
"key=value",
&headers,
Protocol::Http2,
Transport::Tls13,
)
.unwrap();
assert_canonical_equal(&req1, &req2);
}
#[test]
fn test_no_decode_config() {
let config = NormalizeConfig {
decode_path: false,
merge_headers: true,
normalize_case: true,
};
let headers = [("Host", "example.com")];
let req = normalize_request_with_config(
Method::Get,
"https",
"example.com",
"/path%20with%20spaces",
"",
&headers,
Protocol::Http1,
Transport::Tls13,
config,
)
.unwrap();
assert_eq!(req.path_str(), "/path%20with%20spaces");
}
#[test]
fn test_full_request_pipeline_http1_http2_http3() {
let headers_h1 = [
("User-Agent", "Zenith/1.0"),
("Accept", "application/json"),
("Content-Type", "text/plain"),
];
let req_h1 = normalize_request(
Method::Post,
"https",
"api.example.com",
"/v1/resource",
"expand=true",
&headers_h1,
Protocol::Http1,
Transport::Tls13,
)
.unwrap();
let headers_h2 = [
("user-agent", "Zenith/1.0"),
("accept", "application/json"),
("content-type", "text/plain"),
];
let req_h2 = normalize_request(
Method::Post,
"https",
"api.example.com",
"/v1/resource",
"expand=true",
&headers_h2,
Protocol::Http2,
Transport::Tls13,
)
.unwrap();
let headers_h3 = [
("user-agent", "Zenith/1.0"),
("accept", "application/json"),
("content-type", "text/plain"),
];
let req_h3 = normalize_request(
Method::Post,
"https",
"api.example.com",
"/v1/resource",
"expand=true",
&headers_h3,
Protocol::Http3,
Transport::Tls13,
)
.unwrap();
assert_canonical_equal(&req_h1, &req_h2);
assert_canonical_equal(&req_h1, &req_h3);
assert_canonical_equal(&req_h2, &req_h3);
assert_eq!(req_h1.method, Method::Post);
assert_eq!(req_h1.protocol, Protocol::Http1);
assert_eq!(req_h3.protocol, Protocol::Http3);
assert_eq!(req_h1.scheme_str(), "https");
assert_eq!(req_h1.authority_str(), "api.example.com");
assert_eq!(req_h1.path_str(), "/v1/resource");
assert_eq!(req_h1.query_str(), "expand=true");
}