use std::str::FromStr as _;
use chrono::{DateTime, TimeZone as _, Utc};
use printobserver_types::contract::Sample;
use printobserver_types::{
EventKind, EventRecord, FileName, FileNameRefusal, PrintId, Range, RawBytes, Reported,
Timestamp,
};
const FACTOR_RANGE: Range = Range::new(0.1, 10.0);
#[test]
fn a_minted_identifier_is_a_version_seven_uuid() {
let minted = PrintId::new();
assert_eq!(minted.as_uuid().get_version_num(), 7);
let displayed = minted.to_string();
assert_eq!(displayed.parse::<PrintId>(), Ok(minted));
assert_eq!(PrintId::default().as_uuid().get_version_num(), 7);
assert_ne!(
PrintId::new(),
PrintId::new(),
"two mintings are the same identifier"
);
}
#[test]
fn a_refused_identifier_says_which_type_refused_it() {
let error = PrintId::from_str("nonsense").expect_err("the spelling is refused");
assert_eq!(error.type_name(), "PrintId");
assert!(error.detail().contains("nonsense"), "{}", error.detail());
assert!(error.to_string().contains("PrintId"));
for refused in [
"0191f0a0-0000-4000-8000-000000000001",
"0191F0A0-0000-7000-8000-000000000001",
"{0191f0a0-0000-7000-8000-000000000001}",
"0191f0a0000070008000000000000001",
] {
assert!(
PrintId::from_str(refused).is_err(),
"{refused} was accepted"
);
}
}
#[test]
fn an_instant_is_the_utc_instant_it_was_built_from() {
let epoch = Timestamp::from_unix_seconds(0).expect("the epoch is representable");
assert_eq!(epoch.to_string(), "1970-01-01T00:00:00Z");
assert_eq!(
*epoch.as_utc(),
DateTime::<Utc>::from_timestamp(0, 0).expect("the epoch")
);
let from_chrono: Timestamp = Utc.with_ymd_and_hms(2026, 3, 1, 12, 0, 0).unwrap().into();
assert_eq!(from_chrono, Timestamp::sample_full());
assert!(Timestamp::now() > epoch, "now is not after the epoch");
assert!(
Timestamp::from_unix_seconds(i64::MAX).is_err(),
"an unrepresentable instant parsed"
);
}
#[test]
fn the_latest_instant_is_the_last_one_that_reads_back() {
let latest = Timestamp::latest();
assert_eq!(latest.to_string(), "9999-12-31T23:59:59.999999999Z");
assert_eq!(Timestamp::from_str(&latest.to_string()), Ok(latest));
let beyond: Timestamp = (*latest.as_utc() + chrono::TimeDelta::nanoseconds(1)).into();
assert!(
Timestamp::from_str(&beyond.to_string()).is_err(),
"{beyond} read back, so a later instant than the latest is writable"
);
assert!(
beyond.to_string() < latest.to_string(),
"{beyond} orders after {latest} as text"
);
}
#[test]
fn a_refused_instant_says_what_was_wrong() {
let error = Timestamp::from_str("the first of March").expect_err("the string is refused");
assert!(
error.detail().contains("the first of March"),
"{}",
error.detail()
);
assert!(error.to_string().contains("RFC 3339"));
assert!(
Timestamp::from_str("2026-03-01T12:00:00").is_err(),
"an offsetless instant parsed"
);
}
fn identical(left: f64, right: f64) -> bool {
left.to_bits() == right.to_bits()
}
#[test]
fn a_reported_value_carries_what_it_was_given() {
let inside = Reported::new(1.0, FACTOR_RANGE);
assert!(
identical(inside.value(), 1.0),
"{} is not the value it was given",
inside.value()
);
assert!(!inside.out_of_range());
assert_eq!(inside.to_string(), "1");
let outside = Reported::new(40.0, FACTOR_RANGE);
assert!(
identical(outside.value(), 40.0),
"{} is not the value it was given",
outside.value()
);
assert!(outside.out_of_range());
assert_eq!(outside.to_string(), "40 (out of range)");
let range = Range::new(0.0, 1.0);
assert!(range.contains(0.0) && range.contains(1.0) && range.contains(0.5));
assert!(!range.contains(-0.1) && !range.contains(1.1) && !range.contains(f64::NAN));
}
#[test]
fn raw_bytes_are_held_exactly_as_received() {
let held = RawBytes::new(b"{\"event\":{}}".to_vec());
assert_eq!(held.as_slice(), b"{\"event\":{}}");
assert_eq!(held.to_string(), "eyJldmVudCI6e319");
assert!(RawBytes::default().as_slice().is_empty());
assert!(
serde_json::from_value::<RawBytes>(serde_json::json!("not base64!")).is_err(),
"a string that is no base64 was read as bytes"
);
}
#[test]
fn a_file_name_is_the_characters_it_was_given() {
let name = FileName::new("benchy.gcode").expect("a name carrying no forbidden property");
assert_eq!(name.as_str(), "benchy.gcode");
assert_eq!(name.to_string(), "benchy.gcode");
assert_eq!(FileName::from_str("benchy.gcode"), Ok(name));
let error = FileName::new("../etc/passwd").expect_err("a parent-directory segment");
assert_eq!(error.refusal(), FileNameRefusal::Separator);
assert!(error.to_string().contains("separator"), "{error}");
for refusal in FileNameRefusal::ALL {
assert!(!refusal.detail().is_empty());
assert_eq!(refusal.to_string(), refusal.detail());
}
}
#[test]
fn the_envelope_sample_is_under_a_kind_of_its_own() {
assert_eq!(EventRecord::sample_full().kind(), &EventKind::sample_full());
assert_eq!(EventKind::sample_full().as_str(), "sample_event");
}