use crate::NHSNumber;
use rand::RngExt;
use std::ops::RangeInclusive;
use std::sync::LazyLock;
#[allow(dead_code)]
pub static TESTABLE_MIN: LazyLock<NHSNumber> = LazyLock::new(|| NHSNumber {
digits: [9, 9, 9, 0, 0, 0, 0, 0, 0, 0],
});
#[allow(dead_code)]
pub static TESTABLE_MAX: LazyLock<NHSNumber> = LazyLock::new(|| NHSNumber {
digits: [9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
});
#[allow(dead_code)]
pub static TESTABLE_RANGE_INCLUSIVE: LazyLock<RangeInclusive<NHSNumber>> =
LazyLock::new(|| RangeInclusive::new(*TESTABLE_MIN, *TESTABLE_MAX));
#[allow(dead_code)]
pub fn testable_random_sample() -> NHSNumber {
let mut rng = rand::rng();
NHSNumber {
digits: [
9,
9,
9,
rng.random_range(0..=9) as i8,
rng.random_range(0..=9) as i8,
rng.random_range(0..=9) as i8,
rng.random_range(0..=9) as i8,
rng.random_range(0..=9) as i8,
rng.random_range(0..=9) as i8,
rng.random_range(0..=9) as i8,
],
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_min_exact_value() {
assert_eq!(TESTABLE_MIN.digits, [9, 9, 9, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(TESTABLE_MIN.to_string(), "999 000 0000");
}
#[test]
fn test_max_exact_value() {
assert_eq!(TESTABLE_MAX.digits, [9; 10]);
assert_eq!(TESTABLE_MAX.to_string(), "999 999 9999");
}
#[test]
fn test_min_strictly_less_than_max() {
assert!(*TESTABLE_MIN < *TESTABLE_MAX);
}
#[test]
fn test_range_inclusive_contains_endpoints() {
assert!(TESTABLE_RANGE_INCLUSIVE.contains(&*TESTABLE_MIN));
assert!(TESTABLE_RANGE_INCLUSIVE.contains(&*TESTABLE_MAX));
}
#[test]
fn test_range_inclusive_contains_interior() {
let interior = NHSNumber::new([9, 9, 9, 1, 2, 3, 4, 5, 6, 7]);
assert!(TESTABLE_RANGE_INCLUSIVE.contains(&interior));
}
#[test]
fn test_range_inclusive_excludes_below() {
let below = NHSNumber::new([9, 9, 8, 9, 9, 9, 9, 9, 9, 9]);
assert!(!TESTABLE_RANGE_INCLUSIVE.contains(&below));
}
#[test]
fn test_range_inclusive_excludes_low_numbers() {
let zeros = NHSNumber::new([0; 10]);
assert!(!TESTABLE_RANGE_INCLUSIVE.contains(&zeros));
}
#[test]
fn test_range_inclusive_excludes_issued_ranges() {
let issued = NHSNumber::new([6, 0, 0, 0, 0, 0, 0, 0, 0, 0]); assert!(!TESTABLE_RANGE_INCLUSIVE.contains(&issued));
}
#[test]
fn test_random_sample_in_range() {
for _ in 0..64 {
let n = testable_random_sample();
assert!(n >= *TESTABLE_MIN);
assert!(n <= *TESTABLE_MAX);
assert!(TESTABLE_RANGE_INCLUSIVE.contains(&n));
}
}
#[test]
fn test_random_sample_first_three_digits_are_999() {
for _ in 0..64 {
let n = testable_random_sample();
assert_eq!(&n.digits[0..3], &[9, 9, 9]);
}
}
#[test]
fn test_random_sample_all_remaining_digits_in_range() {
for _ in 0..64 {
let n = testable_random_sample();
for d in &n.digits[3..] {
assert!((0..=9).contains(d), "digit out of range: {d}");
}
}
}
#[test]
fn test_random_sample_is_non_deterministic() {
let samples: Vec<_> = (0..16).map(|_| testable_random_sample()).collect();
let distinct: std::collections::BTreeSet<_> = samples.iter().copied().collect();
assert!(distinct.len() > 1, "samples were unexpectedly identical");
}
}