use oauth_as::{Scope, ScopeSet};
#[test]
fn space_is_rejected_because_it_is_the_delimiter_not_content() {
assert!(Scope::new("has space").is_err());
assert!(Scope::new(" ").is_err());
assert!(Scope::new("trailing ").is_err());
assert!(Scope::new(" leading").is_err());
}
#[test]
fn double_quote_is_rejected() {
assert!(Scope::new("dq\"uote").is_err());
assert!(Scope::new("\"").is_err());
}
#[test]
fn backslash_is_rejected() {
assert!(Scope::new("back\\slash").is_err());
assert!(Scope::new("\\").is_err());
}
#[test]
fn the_full_legal_range_is_accepted() {
let mut token = String::new();
for b in 0x21u8..=0x7E {
if b == 0x22 || b == 0x5C {
continue; }
token.push(b as char);
}
assert!(
Scope::new(token.clone()).is_ok(),
"the full RFC 6749 s3.3 legal range must be accepted, got rejected: {token:?}"
);
assert!(Scope::new("!").is_ok(), "0x21, the lower bound");
assert!(Scope::new("[").is_ok(), "0x5B, end of the first sub-range");
assert!(
Scope::new("]").is_ok(),
"0x5D, start of the second sub-range"
);
assert!(Scope::new("~").is_ok(), "0x7E, the upper bound");
}
#[test]
fn control_bytes_and_del_are_rejected() {
assert!(Scope::new("\u{1}").is_err(), "0x01, a C0 control byte");
assert!(
Scope::new("\u{7f}").is_err(),
"0x7F, DEL, just past the upper bound"
);
}
#[test]
fn non_ascii_is_rejected() {
assert!(Scope::new("caf\u{e9}").is_err());
}
#[test]
fn empty_token_is_rejected() {
assert!(Scope::new("").is_err());
}
#[test]
fn duplicate_tokens_collapse_and_ordering_is_deterministic() {
let a = ScopeSet::parse("write read read write admin").unwrap();
assert_eq!(a.len(), 3, "duplicates must collapse to one entry each");
assert_eq!(a.to_string(), "admin read write");
let b = ScopeSet::parse("admin write read").unwrap();
assert_eq!(a, b);
assert_eq!(a.to_string(), b.to_string());
}
#[test]
fn repeated_whitespace_between_tokens_is_tolerated() {
let set = ScopeSet::parse(" read write ").unwrap();
assert_eq!(set.len(), 2);
assert_eq!(set.to_string(), "read write");
}
#[test]
fn empty_scope_round_trips() {
let empty = ScopeSet::empty();
assert!(empty.is_empty());
assert_eq!(empty.len(), 0);
assert_eq!(empty.to_string(), "");
assert_eq!(ScopeSet::parse("").unwrap(), empty);
assert_eq!(
ScopeSet::parse(" ").unwrap(),
empty,
"whitespace only is still empty"
);
}
#[test]
fn subset_semantics_cover_equal_strict_disjoint_and_empty() {
let all = ScopeSet::parse("a b c").unwrap();
let equal = ScopeSet::parse("c b a").unwrap();
let strict_subset = ScopeSet::parse("a c").unwrap();
let strict_superset = ScopeSet::parse("a b c d").unwrap();
let disjoint = ScopeSet::parse("x y z").unwrap();
let partially_overlapping = ScopeSet::parse("a x").unwrap();
assert!(equal.is_subset(&all), "a set is a subset of an equal set");
assert!(strict_subset.is_subset(&all));
assert!(
!strict_superset.is_subset(&all),
"a strict superset must not read as a subset"
);
assert!(!disjoint.is_subset(&all), "disjoint sets share no tokens");
assert!(
!partially_overlapping.is_subset(&all),
"partial overlap is not a subset: EVERY token must be covered"
);
assert!(ScopeSet::empty().is_subset(&all));
assert!(ScopeSet::empty().is_subset(&ScopeSet::empty()));
assert!(!all.is_subset(&ScopeSet::empty()));
}
#[test]
fn serialization_is_space_delimited_and_roundtrips_through_json() {
let set = ScopeSet::parse("write read admin").unwrap();
let json = serde_json::to_string(&set).unwrap();
assert_eq!(json, "\"admin read write\"");
let back: ScopeSet = serde_json::from_str(&json).unwrap();
assert_eq!(back, set);
assert_eq!(back.to_string(), set.to_string());
}
#[test]
fn one_malformed_token_fails_the_whole_parse() {
assert!(ScopeSet::parse("read \"write\" admin").is_err());
}