//! Tests for identifiers and primitive values. Do not edit: these define the required behaviour. use proto::{CallId, DataClass, Epoch, Hash32, SessionId, Timestamp, ValueError}; #[test] fn session_id_accepts_lowercase_digits_and_hyphen() { for ok in ["a", "mm-thread-42", "0", "a-b-c", &"x".repeat(64)] { assert_eq!(SessionId::new(ok).unwrap().as_str(), ok); } } #[test] fn session_id_rejects_everything_else() { let too_long = "x".repeat(65); for bad in [ "", "A", "a b", "a/b", "../etc", "a.b", "a_b", "é", "a\n", too_long.as_str(), ] { assert_eq!( SessionId::new(bad), Err(ValueError::SessionId), "accepted {bad:?}" ); } } #[test] fn session_id_json_is_a_plain_string_and_is_validated() { let id = SessionId::new("mm-thread-42").unwrap(); assert_eq!(serde_json::to_string(&id).unwrap(), r#""mm-thread-42""#); assert_eq!( serde_json::from_str::(r#""mm-thread-42""#).unwrap(), id ); assert!(serde_json::from_str::(r#""../etc""#).is_err()); assert!(serde_json::from_str::("42").is_err()); } #[test] fn epoch_and_call_id_are_plain_numbers() { assert_eq!(serde_json::to_string(&Epoch(3)).unwrap(), "3"); assert_eq!( serde_json::to_string(&CallId(18446744073709551615)).unwrap(), "18446744073709551615" ); assert_eq!(serde_json::from_str::("7").unwrap(), CallId(7)); assert!(serde_json::from_str::("-1").is_err()); assert!(serde_json::from_str::("1.5").is_err()); assert!(serde_json::from_str::("4294967296").is_err()); } #[test] fn hash32_hex_round_trip() { let mut bytes = [0u8; 32]; for (i, b) in bytes.iter_mut().enumerate() { *b = i as u8; } let h = Hash32::from_bytes(bytes); let hex = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; assert_eq!(h.to_hex(), hex); assert_eq!(Hash32::from_hex(hex).unwrap(), h); assert_eq!(h.as_bytes(), &bytes); assert_eq!(serde_json::to_string(&h).unwrap(), format!("\"{hex}\"")); assert_eq!( serde_json::from_str::(&format!("\"{hex}\"")).unwrap(), h ); assert_eq!(Hash32::ZERO.to_hex(), "0".repeat(64)); } #[test] fn hash32_rejects_wrong_length_uppercase_and_non_hex() { let upper = "000102030405060708090A0B0C0D0E0F101112131415161718191a1b1c1d1e1f"; let non_hex = "g00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; let non_ascii = format!("é{}", "0".repeat(62)); for bad in [ "", "00", &"0".repeat(63), &"0".repeat(65), upper, non_hex, non_ascii.as_str(), ] { assert_eq!( Hash32::from_hex(bad), Err(ValueError::Hash32), "accepted {bad:?}" ); } } #[test] fn timestamp_has_one_spelling() { let t = Timestamp::from_unix_millis(1_789_632_300_000); assert_eq!(t.unix_millis(), 1_789_632_300_000); assert_eq!(t.to_rfc3339(), "2026-09-17T08:05:00.000Z"); assert_eq!(Timestamp::parse("2026-09-17T08:05:00.000Z").unwrap(), t); assert_eq!( serde_json::to_string(&t).unwrap(), r#""2026-09-17T08:05:00.000Z""# ); assert_eq!( Timestamp::from_unix_millis(0).to_rfc3339(), "1970-01-01T00:00:00.000Z" ); assert_eq!( Timestamp::from_unix_millis(1_789_632_300_007).to_rfc3339(), "2026-09-17T08:05:00.007Z" ); } #[test] fn timestamp_rejects_other_spellings() { for bad in [ "2026-09-17T08:05:00Z", "2026-09-17T08:05:00.0Z", "2026-09-17T08:05:00.000000Z", "2026-09-17 08:05:00.000Z", "2026-09-17T08:05:00.000+00:00", "2026-09-17t08:05:00.000z", "2026-09-17", "", "now", ] { assert_eq!( Timestamp::parse(bad), Err(ValueError::Timestamp), "accepted {bad:?}" ); assert!(serde_json::from_str::(&format!("\"{bad}\"")).is_err()); } assert!(serde_json::from_str::("1789632300000").is_err()); } #[test] fn timestamp_now_is_after_2026() { assert!(Timestamp::now() > Timestamp::parse("2026-01-01T00:00:00.000Z").unwrap()); } #[test] fn data_class_is_ordered_and_lowercase() { assert!(DataClass::Public < DataClass::Private); assert!(DataClass::Private < DataClass::Secret); assert_eq!(DataClass::Private.max(DataClass::Secret), DataClass::Secret); assert_eq!( serde_json::to_string(&DataClass::Secret).unwrap(), r#""secret""# ); assert_eq!( serde_json::from_str::(r#""public""#).unwrap(), DataClass::Public ); assert!(serde_json::from_str::(r#""Public""#).is_err()); assert!(serde_json::from_str::(r#""internal""#).is_err()); } #[test] fn value_error_is_a_std_error_with_a_message() { let e: Box = Box::new(ValueError::Hash32); assert!(!e.to_string().is_empty()); }