use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct CoverageRange {
pub min: f64,
pub target: f64,
pub max: f64,
}
impl CoverageRange {
pub fn new(min: f64, target: f64, max: f64) -> Self {
assert!(
(0.0..=100.0).contains(&min),
"min must be between 0 and 100"
);
assert!(
(0.0..=100.0).contains(&target),
"target must be between 0 and 100"
);
assert!(
(0.0..=100.0).contains(&max),
"max must be between 0 and 100"
);
assert!(min <= target, "min must be <= target");
assert!(target <= max, "target must be <= max");
Self { min, target, max }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum GapSeverity {
None,
Minor,
Moderate,
Critical,
}
impl GapSeverity {
pub fn emoji(&self) -> &'static str {
match self {
GapSeverity::None => "🟢",
GapSeverity::Minor => "🟡",
GapSeverity::Moderate => "🟠",
GapSeverity::Critical => "🔴",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct CoverageGap {
pub actual: f64,
pub expected: f64,
pub gap: f64,
pub severity: GapSeverity,
}
impl CoverageGap {
pub fn calculate(actual: f64, range: &CoverageRange) -> Self {
let expected = range.target;
let gap = expected - actual;
let severity = if actual >= range.target {
GapSeverity::None
} else if actual >= range.min {
GapSeverity::Minor
} else if actual >= range.min * 0.5 {
GapSeverity::Moderate
} else {
GapSeverity::Critical
};
Self {
actual,
expected,
gap,
severity,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CoverageExpectations {
pub pure: CoverageRange,
pub business_logic: CoverageRange,
pub state_management: CoverageRange,
pub io_operations: CoverageRange,
pub validation: CoverageRange,
pub error_handling: CoverageRange,
pub configuration: CoverageRange,
pub initialization: CoverageRange,
pub orchestration: CoverageRange,
pub utilities: CoverageRange,
pub debug: CoverageRange,
pub performance: CoverageRange,
}
impl Default for CoverageExpectations {
fn default() -> Self {
Self {
pure: CoverageRange::new(90.0, 95.0, 100.0),
business_logic: CoverageRange::new(80.0, 90.0, 95.0),
state_management: CoverageRange::new(75.0, 85.0, 90.0),
io_operations: CoverageRange::new(60.0, 70.0, 80.0),
validation: CoverageRange::new(85.0, 92.0, 98.0),
error_handling: CoverageRange::new(70.0, 80.0, 90.0),
configuration: CoverageRange::new(60.0, 70.0, 80.0),
initialization: CoverageRange::new(50.0, 65.0, 75.0),
orchestration: CoverageRange::new(65.0, 75.0, 85.0),
utilities: CoverageRange::new(75.0, 85.0, 95.0),
debug: CoverageRange::new(20.0, 30.0, 40.0),
performance: CoverageRange::new(40.0, 50.0, 60.0),
}
}
}
impl CoverageExpectations {
pub fn for_role(&self, role: &str) -> &CoverageRange {
match role {
"Pure" => &self.pure,
"BusinessLogic" => &self.business_logic,
"StateManagement" => &self.state_management,
"IoOperations" => &self.io_operations,
"Validation" => &self.validation,
"ErrorHandling" => &self.error_handling,
"Configuration" => &self.configuration,
"Initialization" => &self.initialization,
"Orchestration" => &self.orchestration,
"Utilities" => &self.utilities,
"Debug" => &self.debug,
"Performance" => &self.performance,
_ => &self.business_logic, }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_coverage_range_validation() {
let range = CoverageRange::new(80.0, 90.0, 100.0);
assert_eq!(range.min, 80.0);
assert_eq!(range.target, 90.0);
assert_eq!(range.max, 100.0);
}
#[test]
#[should_panic(expected = "min must be <= target")]
fn test_coverage_range_invalid_order() {
CoverageRange::new(90.0, 80.0, 100.0);
}
#[test]
fn test_gap_severity_none() {
let range = CoverageRange::new(80.0, 90.0, 100.0);
let gap = CoverageGap::calculate(95.0, &range);
assert_eq!(gap.severity, GapSeverity::None);
assert_eq!(gap.severity.emoji(), "🟢");
}
#[test]
fn test_gap_severity_minor() {
let range = CoverageRange::new(80.0, 90.0, 100.0);
let gap = CoverageGap::calculate(85.0, &range);
assert_eq!(gap.severity, GapSeverity::Minor);
assert_eq!(gap.gap, 5.0);
assert_eq!(gap.severity.emoji(), "🟡");
}
#[test]
fn test_gap_severity_moderate() {
let range = CoverageRange::new(80.0, 90.0, 100.0);
let gap = CoverageGap::calculate(50.0, &range); assert_eq!(gap.severity, GapSeverity::Moderate);
assert_eq!(gap.severity.emoji(), "🟠");
}
#[test]
fn test_gap_severity_critical() {
let range = CoverageRange::new(80.0, 90.0, 100.0);
let gap = CoverageGap::calculate(30.0, &range); assert_eq!(gap.severity, GapSeverity::Critical);
assert_eq!(gap.severity.emoji(), "🔴");
}
#[test]
fn test_default_expectations() {
let expectations = CoverageExpectations::default();
assert_eq!(expectations.pure.target, 95.0);
assert_eq!(expectations.debug.target, 30.0);
assert_eq!(expectations.business_logic.target, 90.0);
}
#[test]
fn test_for_role() {
let expectations = CoverageExpectations::default();
assert_eq!(
expectations.for_role("Pure").target,
expectations.pure.target
);
assert_eq!(
expectations.for_role("Debug").target,
expectations.debug.target
);
assert_eq!(
expectations.for_role("Validation").target,
expectations.validation.target
);
assert_eq!(
expectations.for_role("Unknown").target,
expectations.business_logic.target
);
}
}