use rsurl::Url;
#[test]
fn ipv6_literal_with_port() {
let u = Url::parse("http://[::1]:8080/path?q=1").expect("ipv6 url parses");
assert_eq!(u.scheme, "http");
assert_eq!(u.path, "/path?q=1");
assert!(
u.host.contains("::1"),
"host should retain ipv6 literal, got {:?}",
u.host,
);
}
#[test]
fn percent_encoded_path_passthrough() {
let u = Url::parse("http://example.com/foo%20bar/%2Fbaz?a=%26b").unwrap();
assert_eq!(u.scheme, "http");
assert_eq!(u.host, "example.com");
assert_eq!(u.path, "/foo%20bar/%2Fbaz?a=%26b");
}
#[test]
fn query_kept_fragment_stripped() {
let u = Url::parse("http://h/p?x=1&y=2#section").unwrap();
assert_eq!(u.path, "/p?x=1&y=2");
}
#[test]
fn missing_path_defaults_to_slash() {
let u = Url::parse("http://example.com").unwrap();
assert_eq!(u.path, "/");
assert_eq!(u.port, 80);
}
#[test]
fn default_https_port_and_explicit_override() {
assert_eq!(Url::parse("https://h/").unwrap().port, 443);
assert_eq!(Url::parse("https://h:8443/").unwrap().port, 8443);
}
#[test]
fn userinfo_with_password() {
let u = Url::parse("http://alice:s%3Acret@h/p").unwrap();
assert_eq!(u.userinfo.as_deref(), Some("alice:s%3Acret"));
assert_eq!(u.host, "h");
assert_eq!(u.path, "/p");
}
#[test]
fn rejects_empty_host() {
assert!(Url::parse("http:///path").is_err());
}
#[test]
fn rejects_bare_path() {
assert!(Url::parse("/just/a/path").is_err());
}
fn serve_capture(listener: std::net::TcpListener) -> std::thread::JoinHandle<String> {
use std::io::{Read, Write};
std::thread::spawn(move || {
let (mut sock, _) = listener.accept().unwrap();
let mut head = Vec::new();
let mut byte = [0u8; 1];
while !head.ends_with(b"\r\n\r\n") && sock.read(&mut byte).unwrap_or(0) == 1 {
head.push(byte[0]);
}
let _ =
sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok");
String::from_utf8_lossy(&head).into_owned()
})
}
#[test]
fn get_over_ipv6_literal_dials_and_keeps_brackets_in_host_header() {
let Ok(listener) = std::net::TcpListener::bind("[::1]:0") else {
eprintln!("no IPv6 loopback; skipping");
return;
};
let port = listener.local_addr().unwrap().port();
let server = serve_capture(listener);
let resp = rsurl::Request::get(&format!("http://[::1]:{port}/v6?x=1"))
.unwrap()
.send()
.expect("GET over [::1] succeeds");
assert_eq!(resp.status, 200);
assert_eq!(resp.body, b"ok");
let head = server.join().unwrap();
assert!(head.starts_with("GET /v6?x=1 HTTP/1.1\r\n"), "{head}");
assert!(
head.to_ascii_lowercase()
.contains(&format!("\r\nhost: [::1]:{port}\r\n")),
"{head}"
);
}
#[test]
fn request_line_has_root_path_and_no_dot_segments() {
for (path_part, want) in [("?q=1", "/?q=1"), ("/a/b/../c/./d", "/a/c/d")] {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();
let server = serve_capture(listener);
let resp = rsurl::Request::get(&format!("http://127.0.0.1:{port}{path_part}"))
.unwrap()
.send()
.unwrap();
assert_eq!(resp.status, 200);
let head = server.join().unwrap();
assert!(
head.starts_with(&format!("GET {want} HTTP/1.1\r\n")),
"{path_part}: {head}"
);
}
}