use proptest::prelude::*;
use waggle_core::{
mint, CanonicalUrl, Channel, Disposition, MatchExpr, MintOptions, MintSpec, Sharer, Stage,
Timestamp, Token, VariantBody, TOKEN_ALPHABET,
};
fn seeded_entropy(seed: u32) -> impl FnMut(&mut [u8]) -> Result<(), waggle_core::EntropyError> {
let mut state = seed | 1; move |buf: &mut [u8]| {
for b in buf.iter_mut() {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
*b = (state & 0xFF) as u8;
}
Ok(())
}
}
fn arb_slug() -> impl Strategy<Value = String> {
proptest::string::string_regex("[a-z0-9][a-z0-9._/-]{0,30}").expect("valid regex")
}
proptest! {
#[test]
fn p1_tokens_stay_inside_the_alphabet(seed: u32, len in 1usize..=23) {
let mut entropy = seeded_entropy(seed);
let t = Token::generate(len, &mut entropy).unwrap();
prop_assert_eq!(t.as_str().len(), len);
prop_assert!(t.as_str().bytes().all(|b| TOKEN_ALPHABET.contains(&b)));
}
#[test]
fn p2_token_string_roundtrip(seed: u32) {
let mut entropy = seeded_entropy(seed);
let t = Token::generate(8, &mut entropy).unwrap();
prop_assert_eq!(Token::parse(&t.to_string()).unwrap(), t);
}
#[test]
fn p3_slug_normalization_idempotent(raw in arb_slug()) {
if let Ok(once) = Channel::new(&raw) {
let twice = Channel::new(once.as_str()).unwrap();
prop_assert_eq!(once, twice);
}
if let Ok(once) = Stage::new(&raw) {
let twice = Stage::new(once.as_str()).unwrap();
prop_assert_eq!(once, twice);
}
}
#[test]
fn p4_slug_rejects_or_normalizes_everything(raw in ".{0,80}") {
if let Ok(s) = Sharer::new(&raw) {
prop_assert!(!s.as_str().is_empty());
prop_assert!(s.as_str().bytes().all(|b| b.is_ascii_lowercase()
|| b.is_ascii_digit() || matches!(b, b'-' | b'_' | b'.' | b'/')));
}
}
#[test]
fn p5_one_call_mint_is_total(seed: u32, now_ms: u64) {
let mut entropy = seeded_entropy(seed);
let now = Timestamp::from_unix_ms(now_ms);
let m = mint(
MintSpec::new(
CanonicalUrl::new("ws://a/artifact").unwrap(),
Sharer::new("lead").unwrap(),
Channel::subagent_general(),
),
&MintOptions::default(),
&mut entropy,
now,
)
.unwrap();
let catch_alls = m.variants.iter().filter(|v| v.match_expr.is_catch_all()).count();
prop_assert_eq!(catch_alls, 1);
prop_assert_eq!(m.version, 1);
prop_assert_eq!(m.disposition(now), Disposition::Active);
}
#[test]
fn p6_manifest_serde_roundtrip(seed: u32, ttl in proptest::option::of(0u64..1_000_000), body in "[ -~]{0,200}") {
let mut entropy = seeded_entropy(seed);
let mut spec = MintSpec::new(
CanonicalUrl::new("file:///tmp/report.md").unwrap(),
Sharer::new("orchestrator").unwrap(),
Channel::new("subagent/pricing").unwrap(),
)
.variant(
MatchExpr::any(),
VariantBody::Inline { content_type: "text/markdown".into(), data: body },
);
if let Some(t) = ttl { spec = spec.ttl_ms(t); }
let m = mint(spec, &MintOptions::default(), &mut entropy, Timestamp::from_unix_ms(5)).unwrap();
let json = serde_json::to_string(&m).unwrap();
let back: waggle_core::AttributionManifest = serde_json::from_str(&json).unwrap();
prop_assert_eq!(back, m);
}
#[test]
fn p7_ttl_expiry_boundary(seed: u32, start in 0u64..1_000_000, ttl in 1u64..1_000_000) {
let mut entropy = seeded_entropy(seed);
let now = Timestamp::from_unix_ms(start);
let m = mint(
MintSpec::new(
CanonicalUrl::new("ws://a/b").unwrap(),
Sharer::new("lead").unwrap(),
Channel::subagent_general(),
)
.ttl_ms(ttl),
&MintOptions::default(),
&mut entropy,
now,
)
.unwrap();
let boundary = Timestamp::from_unix_ms(start + ttl);
prop_assert_eq!(m.disposition(Timestamp::from_unix_ms(start + ttl - 1)), Disposition::Active);
prop_assert_eq!(m.disposition(boundary), Disposition::Expired);
}
}
#[test]
fn p8_no_collisions_across_10k_seeds() {
let mut seen = std::collections::HashSet::new();
for seed in 1..=10_000u32 {
let mut entropy = seeded_entropy(seed.wrapping_mul(0x9E37_79B9));
let t = Token::generate(8, &mut entropy).unwrap();
assert!(seen.insert(t.to_string()), "collision at seed {seed}");
}
}