use crate::uri::Uri;
use rama_core::bytes::Bytes;
use ahash::{HashMap, HashMapExt as _, HashSet, HashSetExt as _};
#[test]
fn eq_same_input_parsed_twice_lazy_lazy() {
let a = Uri::parse("https://example.com/path?q=1#f").unwrap();
let b = Uri::parse("https://example.com/path?q=1#f").unwrap();
assert_eq!(a, b);
}
#[test]
fn eq_clone_is_equal_and_hits_arc_ptr_eq_path() {
let a = Uri::parse("https://example.com/p").unwrap();
let b = a.clone();
assert_eq!(a, b);
}
#[test]
fn eq_asterisk_form() {
let a = Uri::parse("*").unwrap();
let b = Uri::parse("*").unwrap();
assert_eq!(a, b);
}
#[test]
fn eq_lazy_owned_after_mutation() {
let mut a = Uri::parse("https://example.com/p").unwrap();
a.set_path("/p"); let b = Uri::parse("https://example.com/p").unwrap();
assert_eq!(a, b);
}
#[test]
fn eq_distinguishes_path_difference() {
let a = Uri::parse("https://example.com/a").unwrap();
let b = Uri::parse("https://example.com/b").unwrap();
assert_ne!(a, b);
}
#[test]
fn eq_distinguishes_query_presence() {
let with_q = Uri::parse("https://example.com/p?").unwrap();
let no_q = Uri::parse("https://example.com/p").unwrap();
assert_ne!(with_q, no_q);
}
#[test]
fn eq_distinguishes_pct_encoded_vs_decoded_path() {
let a = Uri::parse("https://example.com/a%62c").unwrap();
let b = Uri::parse("https://example.com/abc").unwrap();
assert_ne!(a, b);
assert_eq!(a.canonicalize(), b.canonicalize());
}
#[test]
fn path_ref_string_eq_preserves_encoded_slash_boundary() {
let uri = Uri::parse("https://example.com/a%2Fb").unwrap();
let path = uri.path_ref_or_root();
assert_eq!(path, "/a%2Fb");
assert_ne!(path, "/a/b");
assert_eq!(path.segment_count(), 1);
}
#[test]
fn eq_ignores_host_case_via_domain_semantics() {
let a = Uri::parse("https://EXAMPLE.com/p").unwrap();
let b = Uri::parse("https://example.com/p").unwrap();
assert_eq!(a, b);
}
#[test]
fn eq_treats_pct_decoded_host_equivalent() {
let a = Uri::parse("http://exa%2Cmple/p").unwrap();
let b = Uri::parse("http://exa,mple/p").unwrap();
assert_eq!(a, b);
}
#[test]
fn eq_distinguishes_default_port_explicit_vs_implicit() {
let with_port = Uri::parse("https://example.com:443/").unwrap();
let without = Uri::parse("https://example.com/").unwrap();
assert_ne!(with_port, without);
assert_eq!(with_port.canonicalize(), without.canonicalize());
}
#[test]
fn hashmap_lookup_works_with_uri_key() {
let mut m: HashMap<Uri, &'static str> = HashMap::new();
m.insert(Uri::parse("https://example.com/p").unwrap(), "value");
assert_eq!(
m.get(&Uri::parse("https://example.com/p").unwrap()),
Some(&"value")
);
assert!(!m.contains_key(&Uri::parse("https://example.com/q").unwrap()));
}
#[test]
fn hashmap_lookup_works_across_lazy_owned_boundary() {
let mut m: HashMap<Uri, ()> = HashMap::new();
let lazy = Uri::parse("https://example.com/p").unwrap();
m.insert(lazy, ());
let mut owned = Uri::parse("https://example.com/p").unwrap();
owned.set_path("/p"); assert!(m.contains_key(&owned));
}
#[test]
fn hashset_dedup() {
let mut s: HashSet<Uri> = HashSet::new();
s.insert(Uri::parse("http://a.example/").unwrap());
s.insert(Uri::parse("http://a.example/").unwrap());
s.insert(Uri::parse("http://b.example/").unwrap());
assert_eq!(s.len(), 2);
}
#[test]
fn hash_consistent_with_eq_for_pct_encoded_host() {
use crate::test_hash::hash;
let a = Uri::parse("http://exa%2Cmple/p").unwrap();
let b = Uri::parse("http://exa,mple/p").unwrap();
assert_eq!(a, b);
assert_eq!(hash(&a), hash(&b));
let mut m: HashMap<Uri, &'static str> = HashMap::new();
m.insert(a, "value");
assert_eq!(m.get(&b), Some(&"value"));
}
#[test]
fn hash_consistent_with_eq_for_host_case() {
use crate::test_hash::hash;
let a = Uri::parse("https://EXAMPLE.com/p").unwrap();
let b = Uri::parse("https://example.com/p").unwrap();
assert_eq!(a, b);
assert_eq!(hash(&a), hash(&b));
}
#[test]
fn ord_lex_compare_on_wire_form() {
let a = Uri::parse("https://example.com/a").unwrap();
let b = Uri::parse("https://example.com/b").unwrap();
assert!(a < b);
}
#[test]
fn ord_sort_stable_for_routing_table_keys() {
let mut v: Vec<Uri> = [
"https://c.example/",
"https://a.example/",
"https://b.example/p",
"https://b.example/",
]
.into_iter()
.map(|s| Uri::parse(s).unwrap())
.collect();
v.sort();
let rendered: Vec<String> = v.iter().map(Uri::to_string).collect();
assert_eq!(
rendered,
vec![
"https://a.example/".to_owned(),
"https://b.example/".to_owned(),
"https://b.example/p".to_owned(),
"https://c.example/".to_owned(),
]
);
}
#[test]
fn ord_asterisk_sorts_before_all_other_uris() {
let mut v: Vec<Uri> = ["https://example.com/", "/origin-form", "*", "urn:isbn:0"]
.into_iter()
.map(|s| Uri::parse(s).unwrap())
.collect();
v.sort();
assert_eq!(v[0].to_string(), "*", "asterisk must sort first, got {v:?}");
}
#[test]
fn query_display_and_from_str_round_trip() {
use core::str::FromStr as _;
use crate::uri::Query;
let q = Query::from_str("a=1&b=2").unwrap();
assert_eq!(q.to_string(), "a=1&b=2");
let q = Query::from_str("hello world").unwrap();
assert_eq!(q.to_string(), "hello%20world");
}
#[test]
fn query_default_is_empty_and_distinct_from_dummy() {
use crate::uri::Query;
let d = Query::default();
assert_eq!(d.as_encoded_str(), "");
assert_eq!(d.to_string(), "");
}
#[test]
fn fragment_display_and_from_str_round_trip() {
use core::str::FromStr as _;
use crate::uri::Fragment;
let f = Fragment::from_str("section-1.2").unwrap();
assert_eq!(f.to_string(), "section-1.2");
}
#[test]
fn uri_view_caches_all_components() {
let u = Uri::parse("https://alice:secret@example.com:8443/p?q=1#f").unwrap();
let v = u.view();
assert_eq!(v.scheme(), u.scheme());
assert_eq!(v.path().map(|p| p.as_encoded_str()).as_deref(), Some("/p"));
assert_eq!(
v.query().map(|q| q.as_encoded_str()).as_deref(),
Some("q=1")
);
assert_eq!(
v.fragment().map(|f| f.as_encoded_str()).as_deref(),
Some("f")
);
assert_eq!(v.port_u16(), Some(8443));
assert!(v.host().is_some());
assert!(v.userinfo().is_some());
assert!(!v.is_asterisk());
assert!(v.is_absolute());
}
#[test]
fn uri_view_display_matches_source() {
let u = Uri::parse("https://example.com/p?q=1").unwrap();
assert_eq!(u.view().to_string(), u.to_string());
}
#[test]
fn uri_view_asterisk_form_all_components_none() {
let u = Uri::parse("*").unwrap();
let v = u.view();
assert!(v.is_asterisk());
assert!(!v.is_absolute());
assert!(v.scheme().is_none());
assert!(v.authority().is_none());
assert!(v.path().is_none());
assert!(v.query().is_none());
assert!(v.fragment().is_none());
assert!(v.host().is_none());
assert!(v.port().is_unset());
assert!(v.userinfo().is_none());
}
#[test]
fn uri_view_origin_form_no_authority() {
let u = Uri::parse("/p?q=1#f").unwrap();
let v = u.view();
assert!(v.scheme().is_none());
assert!(v.authority().is_none());
assert!(v.host().is_none());
assert!(v.port().is_unset());
assert!(v.userinfo().is_none());
assert_eq!(v.path().map(|p| p.as_encoded_str()).as_deref(), Some("/p"));
assert_eq!(
v.query().map(|q| q.as_encoded_str()).as_deref(),
Some("q=1")
);
assert_eq!(
v.fragment().map(|f| f.as_encoded_str()).as_deref(),
Some("f")
);
}
#[test]
fn query_hash_works_as_btreemap_key() {
use core::str::FromStr as _;
use crate::std::collections::BTreeMap;
use crate::uri::Query;
let mut m: BTreeMap<Query, &'static str> = BTreeMap::new();
m.insert(Query::from_str("a=1").unwrap(), "first");
m.insert(Query::from_str("a=2").unwrap(), "second");
assert_eq!(m.get(&Query::from_str("a=1").unwrap()), Some(&"first"));
assert_eq!(m.len(), 2);
}
#[test]
fn hash_asterisk_distinct_from_other_uris() {
let mut s: ahash::HashSet<Uri> = ahash::HashSet::default();
s.insert(Uri::parse("*").unwrap());
s.insert(Uri::parse("/").unwrap()); s.insert(Uri::parse("https://example.com/").unwrap());
assert_eq!(
s.len(),
3,
"asterisk must hash distinctly from origin/absolute"
);
}
const URI_STR: &str = "https://example.com/path?q=1";
#[test]
fn try_from_str() {
let u = Uri::try_from(URI_STR).unwrap();
assert_eq!(u.host().unwrap().to_str(), "example.com");
}
#[test]
fn try_from_string() {
let u = Uri::try_from(URI_STR.to_owned()).unwrap();
assert_eq!(u.host().unwrap().to_str(), "example.com");
}
#[test]
fn try_from_byte_slice() {
let u = Uri::try_from(URI_STR.as_bytes()).unwrap();
assert_eq!(u.host().unwrap().to_str(), "example.com");
}
#[test]
fn try_from_vec_u8() {
let u = Uri::try_from(URI_STR.as_bytes().to_vec()).unwrap();
assert_eq!(u.host().unwrap().to_str(), "example.com");
}
#[test]
fn try_from_bytes() {
let u = Uri::try_from(Bytes::from_static(URI_STR.as_bytes())).unwrap();
assert_eq!(u.host().unwrap().to_str(), "example.com");
}
#[test]
fn try_from_propagates_parse_error() {
let err = Uri::try_from("").unwrap_err();
assert!(matches!(err, crate::uri::ParseError::Empty));
}
#[test]
fn from_str_direct_via_parse_trait() {
let u: Uri = "https://example.com/p".parse().unwrap();
assert_eq!(u.host().unwrap().to_str(), "example.com");
assert_eq!(u.path().unwrap().as_encoded_str(), "/p");
let r: Result<Uri, _> = "".parse();
assert!(matches!(r, Err(crate::uri::ParseError::Empty)));
}
#[test]
fn try_from_works_at_generic_bound() {
fn accept<T: TryInto<Uri, Error = crate::uri::ParseError>>(input: T) -> Uri {
input.try_into().unwrap()
}
accept(URI_STR);
accept(URI_STR.to_owned());
accept(URI_STR.as_bytes());
accept(URI_STR.as_bytes().to_vec());
accept(Bytes::from_static(URI_STR.as_bytes()));
}
#[test]
fn from_static_parses_canonical_input() {
let u = Uri::from_static("https://example.com/p?q=1#f");
assert_eq!(u.host().unwrap().to_str(), "example.com");
assert_eq!(u.path().unwrap().as_encoded_str(), "/p");
assert_eq!(u.query().unwrap().as_encoded_str(), "q=1");
assert_eq!(u.fragment().unwrap().as_encoded_str(), "f");
}
#[test]
fn from_static_round_trips_through_display() {
let raw = "http://example.com/";
let u = Uri::from_static(raw);
assert_eq!(u.to_string(), raw);
}
#[test]
#[should_panic(expected = "invalid URI")]
fn from_static_panics_on_invalid_with_typed_message() {
let _u = Uri::from_static("http://example.com/\x00path");
}
#[test]
fn is_absolute_for_absolute_form() {
assert!(Uri::parse("https://example.com/p").unwrap().is_absolute());
assert!(Uri::parse("urn:isbn:0451450523").unwrap().is_absolute());
assert!(Uri::parse("mailto:user@example.com").unwrap().is_absolute());
}
#[test]
fn is_absolute_for_origin_form() {
assert!(!Uri::parse("/path").unwrap().is_absolute());
assert!(!Uri::parse("/p?q#f").unwrap().is_absolute());
}
#[test]
fn is_absolute_for_asterisk() {
assert!(!Uri::parse("*").unwrap().is_absolute());
}
#[test]
fn is_absolute_for_relative_reference() {
assert!(!Uri::parse_reference("../foo").unwrap().is_absolute());
assert!(!Uri::parse_reference("?q").unwrap().is_absolute());
assert!(!Uri::parse_reference("#frag").unwrap().is_absolute());
}
#[test]
fn query_ref_eq_str_both_directions() {
let uri = Uri::parse("/p?a=1&b=2").unwrap();
let q = uri.query().unwrap();
assert!(q == "a=1&b=2");
assert!("a=1&b=2" == q);
assert!(q != "a=1");
assert!("a=1" != q);
let good: &str = "a=1&b=2";
let bad: &str = "a=1";
assert!(q == *good);
assert!(*good == q);
assert!(q != *bad);
assert!(*bad != q);
}
#[test]
fn query_ref_eq_str_encodes_raw_component_text() {
let uri = Uri::parse("/p?name=h%20i").unwrap();
let q = uri.query().unwrap();
assert!(q == "name=h i");
assert!("name=h i" == q);
let uri = Uri::parse("/p?a=%41").unwrap();
assert!(uri.query().unwrap() != "a=A");
}
#[test]
fn fragment_ref_eq_str_both_directions() {
let uri = Uri::parse("/p#sec-1").unwrap();
let f = uri.fragment().unwrap();
assert!(f == "sec-1");
assert!("sec-1" == f);
assert!(f != "sec-2");
assert!("sec-2" != f);
let good: &str = "sec-1";
let bad: &str = "sec-2";
assert!(f == *good);
assert!(*good == f);
assert!(f != *bad);
assert!(*bad != f);
let uri = Uri::parse("/p#a%20b").unwrap();
assert!(uri.fragment().unwrap() == "a b");
}