#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normalized_score_always_0_to_100() {
let score = SimpleScore::new(50.0, 100.0, "test");
assert!(score.normalized() >= 0.0);
assert!(score.normalized() <= 100.0);
}
#[test]
fn test_normalized_score_106_scale() {
let score = SimpleScore::new(95.0, 106.0, "Rust Project");
let normalized = score.normalized();
assert!((0.0..=100.0).contains(&normalized));
assert!((normalized - 89.6).abs() < 0.1);
}
#[test]
fn test_normalized_score_110_scale() {
let score = SimpleScore::new(99.0, 110.0, "Repo");
let normalized = score.normalized();
assert!((0.0..=100.0).contains(&normalized));
assert!((normalized - 90.0).abs() < 0.1);
}
#[test]
fn test_normalized_score_200_scale() {
let score = SimpleScore::new(180.0, 200.0, "Perfection");
let normalized = score.normalized();
assert!((0.0..=100.0).contains(&normalized));
assert_eq!(normalized, 90.0);
}
#[test]
fn test_normalized_clamps_negative() {
let score = SimpleScore::new(-10.0, 100.0, "test");
assert_eq!(score.normalized(), 0.0);
}
#[test]
fn test_normalized_clamps_overflow() {
let score = SimpleScore::new(150.0, 100.0, "test");
assert_eq!(score.normalized(), 100.0);
}
#[test]
fn test_grade_boundaries() {
assert_eq!(Grade::from_score(100.0), Grade::A);
assert_eq!(Grade::from_score(90.0), Grade::A);
assert_eq!(Grade::from_score(89.9), Grade::B);
assert_eq!(Grade::from_score(80.0), Grade::B);
assert_eq!(Grade::from_score(79.9), Grade::C);
assert_eq!(Grade::from_score(70.0), Grade::C);
assert_eq!(Grade::from_score(69.9), Grade::D);
assert_eq!(Grade::from_score(60.0), Grade::D);
assert_eq!(Grade::from_score(59.9), Grade::F);
assert_eq!(Grade::from_score(0.0), Grade::F);
}
#[test]
fn test_grade_ordering() {
assert!(Grade::A > Grade::B);
assert!(Grade::B > Grade::C);
assert!(Grade::C > Grade::D);
assert!(Grade::D > Grade::F);
}
#[test]
fn test_meets_threshold() {
let score = SimpleScore::new(85.0, 100.0, "test");
assert!(score.meets_threshold(80.0));
assert!(score.meets_threshold(85.0));
assert!(!score.meets_threshold(90.0));
}
#[test]
fn test_aggregate_score_weighted() {
let mut agg = AggregateScore::new("Combined");
agg.add(SimpleScore::new(80.0, 100.0, "a"), 1.0);
agg.add(SimpleScore::new(100.0, 100.0, "b"), 1.0);
assert_eq!(agg.normalized(), 90.0);
}
#[test]
fn test_aggregate_score_different_weights() {
let mut agg = AggregateScore::new("Weighted");
agg.add(SimpleScore::new(100.0, 100.0, "heavy"), 3.0);
agg.add(SimpleScore::new(0.0, 100.0, "light"), 1.0);
assert_eq!(agg.normalized(), 75.0);
}
#[test]
fn test_aggregate_empty() {
let agg = AggregateScore::new("Empty");
assert_eq!(agg.normalized(), 0.0);
}
#[test]
fn test_display_format() {
let score = SimpleScore::new(85.0, 100.0, "Test Score");
let display = format!("{}", score);
assert!(display.contains("85.0/100"));
assert!(display.contains("B"));
}
#[test]
fn test_from_percentage() {
let score = SimpleScore::from_percentage(75.5, "Coverage");
assert_eq!(score.normalized(), 75.5);
assert_eq!(score.grade(), Grade::C);
}
#[test]
fn test_zero_max_returns_zero() {
struct ZeroMax;
impl NormalizedScore for ZeroMax {
fn raw(&self) -> f64 {
50.0
}
fn max_raw(&self) -> f64 {
0.0
}
}
impl fmt::Display for ZeroMax {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ZeroMax")
}
}
let z = ZeroMax;
assert_eq!(z.normalized(), 0.0);
}
#[test]
fn test_grade_display() {
assert_eq!(format!("{}", Grade::A), "A");
assert_eq!(format!("{}", Grade::B), "B");
assert_eq!(format!("{}", Grade::C), "C");
assert_eq!(format!("{}", Grade::D), "D");
assert_eq!(format!("{}", Grade::F), "F");
}
#[test]
fn test_grade_description() {
assert_eq!(Grade::A.description(), "A (Excellent)");
assert_eq!(Grade::B.description(), "B (Good)");
assert_eq!(Grade::C.description(), "C (Satisfactory)");
assert_eq!(Grade::D.description(), "D (Needs Improvement)");
assert_eq!(Grade::F.description(), "F (Failing)");
}
#[test]
fn test_grade_min_score() {
assert_eq!(Grade::A.min_score(), 90.0);
assert_eq!(Grade::B.min_score(), 80.0);
assert_eq!(Grade::C.min_score(), 70.0);
assert_eq!(Grade::D.min_score(), 60.0);
assert_eq!(Grade::F.min_score(), 0.0);
}
#[test]
fn test_grade_partial_cmp() {
assert!(Grade::A.partial_cmp(&Grade::B) == Some(std::cmp::Ordering::Greater));
assert!(Grade::B.partial_cmp(&Grade::B) == Some(std::cmp::Ordering::Equal));
assert!(Grade::C.partial_cmp(&Grade::B) == Some(std::cmp::Ordering::Less));
}
#[test]
fn test_grade_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(Grade::A);
set.insert(Grade::B);
assert!(set.contains(&Grade::A));
assert!(set.contains(&Grade::B));
assert!(!set.contains(&Grade::C));
}
#[test]
fn test_aggregate_display() {
let mut agg = AggregateScore::new("Combined Score");
agg.add(SimpleScore::new(80.0, 100.0, "a"), 1.0);
agg.add(SimpleScore::new(90.0, 100.0, "b"), 1.0);
let display = format!("{}", agg);
assert!(display.contains("Combined Score"));
assert!(display.contains("85.0"));
}
#[test]
fn test_aggregate_total_weight() {
let mut agg = AggregateScore::new("Test");
agg.add(SimpleScore::new(50.0, 100.0, "a"), 2.0);
agg.add(SimpleScore::new(50.0, 100.0, "b"), 3.0);
assert_eq!(agg.total_weight(), 5.0);
}
#[test]
fn test_aggregate_negative_weight_clamped() {
let mut agg = AggregateScore::new("Test");
agg.add(SimpleScore::new(50.0, 100.0, "a"), -1.0); agg.add(SimpleScore::new(100.0, 100.0, "b"), 1.0);
assert_eq!(agg.total_weight(), 1.0); }
#[test]
fn test_simple_score_raw_and_max() {
let score = SimpleScore::new(75.0, 100.0, "Test");
assert_eq!(score.raw(), 75.0);
assert_eq!(score.max_raw(), 100.0);
}
#[test]
fn test_from_percentage_clamps() {
let high = SimpleScore::from_percentage(150.0, "High");
assert_eq!(high.raw(), 100.0);
let low = SimpleScore::from_percentage(-50.0, "Low");
assert_eq!(low.raw(), 0.0);
}
#[test]
fn test_normalized_score_clone_box() {
let score = SimpleScore::new(80.0, 100.0, "test");
let boxed: Box<dyn NormalizedScoreClone> = Box::new(score);
let cloned = boxed.clone();
assert_eq!(cloned.normalized(), 80.0);
}
#[test]
fn test_normalized_score_clone_debug() {
let score = SimpleScore::new(80.0, 100.0, "test");
let boxed: Box<dyn NormalizedScoreClone> = Box::new(score);
let debug_str = format!("{:?}", boxed);
assert!(debug_str.contains("NormalizedScore"));
assert!(debug_str.contains("80.0"));
}
#[test]
fn test_simple_score_debug() {
let score = SimpleScore::new(75.0, 100.0, "Test");
let debug_str = format!("{:?}", score);
assert!(debug_str.contains("SimpleScore"));
assert!(debug_str.contains("75.0"));
}
#[test]
fn test_aggregate_score_debug() {
let mut agg = AggregateScore::new("Test");
agg.add(SimpleScore::new(50.0, 100.0, "a"), 1.0);
let debug_str = format!("{:?}", agg);
assert!(debug_str.contains("AggregateScore"));
assert!(debug_str.contains("Test"));
}
#[test]
fn test_simple_score_copy() {
let score = SimpleScore::new(70.0, 100.0, "Test");
let copied = score; assert_eq!(copied.normalized(), 70.0);
}
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn normalized_always_in_range(raw in 0.0f64..1000.0, max in 1.0f64..500.0) {
let score = SimpleScore::new(raw, max, "prop");
let normalized = score.normalized();
prop_assert!(normalized >= 0.0, "normalized {} < 0", normalized);
prop_assert!(normalized <= 100.0, "normalized {} > 100", normalized);
}
#[test]
fn grade_monotonic(score1 in 0.0f64..100.0, score2 in 0.0f64..100.0) {
let g1 = Grade::from_score(score1);
let g2 = Grade::from_score(score2);
if score1 > score2 {
prop_assert!(g1 >= g2);
} else if score1 < score2 {
prop_assert!(g1 <= g2);
}
}
#[test]
fn aggregate_bounded(
s1 in 0.0f64..100.0,
s2 in 0.0f64..100.0,
w1 in 0.1f64..10.0,
w2 in 0.1f64..10.0
) {
let mut agg = AggregateScore::new("test");
agg.add(SimpleScore::from_percentage(s1, "a"), w1);
agg.add(SimpleScore::from_percentage(s2, "b"), w2);
let result = agg.normalized();
prop_assert!((0.0..=100.0).contains(&result));
let min = s1.min(s2);
let max = s1.max(s2);
prop_assert!(result >= min - 0.001 && result <= max + 0.001);
}
#[test]
fn threshold_consistent(score in 0.0f64..100.0, threshold in 0.0f64..100.0) {
let s = SimpleScore::from_percentage(score, "test");
let meets = s.meets_threshold(threshold);
if score >= threshold {
prop_assert!(meets);
} else {
prop_assert!(!meets);
}
}
}
}
}