#![cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
use super::super::calculator::PerfectionScoreCalculator;
use super::super::types::{
CategoryScore, CategoryWeights, PerfectionScoreResult, MAX_PERFECTION_SCORE,
};
use proptest::prelude::*;
use std::fs;
use tempfile::TempDir;
proptest! {
#[test]
fn prop_earned_points_proportional(raw_score in 0.0f64..=100.0, max_points in 1u16..=100) {
let score = CategoryScore::new("Test", raw_score, max_points);
let expected = (raw_score / 100.0) * f64::from(max_points);
prop_assert!((score.earned_points - expected).abs() < 0.001);
}
#[test]
fn prop_grade_is_valid(raw_score in 0.0f64..=100.0) {
let score = CategoryScore::new("Test", raw_score, 40);
let valid_grades = ["A+", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D+", "D", "D-", "F"];
prop_assert!(valid_grades.contains(&score.grade.as_str()));
}
#[test]
fn prop_total_score_is_sum(
s1 in 0.0f64..=100.0,
s2 in 0.0f64..=100.0,
s3 in 0.0f64..=100.0
) {
let categories = vec![
CategoryScore::new("A", s1, 40),
CategoryScore::new("B", s2, 30),
CategoryScore::new("C", s3, 30),
];
let result = PerfectionScoreResult::new(categories.clone());
let expected: f64 = categories.iter().map(|c| c.earned_points).sum();
prop_assert!((result.total_score - expected).abs() < 0.001);
}
#[test]
fn prop_score_in_valid_range(raw_score in 0.0f64..=100.0) {
let score = CategoryScore::new("Test", raw_score, 40);
prop_assert!(score.earned_points >= 0.0);
prop_assert!(score.earned_points <= 40.0);
}
#[test]
fn prop_target_gap_correct(score in 0.0f64..=100.0, target in 0u16..=200) {
let categories = vec![CategoryScore::new("Test", score, 100)];
let result = PerfectionScoreResult::new(categories).with_target(target);
let expected_gap = f64::from(target) - (score / 100.0) * 100.0;
prop_assert!((result.target_gap.unwrap() - expected_gap).abs() < 0.001);
}
#[test]
fn prop_weights_sum_to_max(_dummy in 0u8..1) {
let weights = CategoryWeights::default();
let sum = weights.tdg
+ weights.repo_score
+ weights.rust_score
+ weights.popper_score
+ weights.test_coverage
+ weights.mutation
+ weights.documentation
+ weights.performance;
prop_assert_eq!(sum, MAX_PERFECTION_SCORE);
}
#[test]
fn prop_grade_monotonic(score1 in 0.0f64..=200.0, score2 in 0.0f64..=200.0) {
let grade1 = PerfectionScoreResult::calculate_overall_grade(score1);
let grade2 = PerfectionScoreResult::calculate_overall_grade(score2);
fn grade_value(grade: &str) -> u8 {
match grade {
"A+" => 12, "A" => 11, "A-" => 10,
"B+" => 9, "B" => 8, "B-" => 7,
"C+" => 6, "C" => 5, "C-" => 4,
"D+" => 3, "D" => 2, "D-" => 1,
"F" => 0,
_ => 0,
}
}
if score1 > score2 + 1.0 {
prop_assert!(grade_value(&grade1) >= grade_value(&grade2));
}
}
#[test]
fn prop_with_details_preserves_fields(
raw_score in 0.0f64..=100.0,
max_points in 1u16..=100
) {
let score = CategoryScore::new("Test", raw_score, max_points);
let score_with_details = score.clone().with_details("some details");
prop_assert_eq!(score.name, score_with_details.name);
prop_assert_eq!(score.raw_score, score_with_details.raw_score);
prop_assert_eq!(score.max_points, score_with_details.max_points);
prop_assert_eq!(score.earned_points, score_with_details.earned_points);
prop_assert_eq!(score.grade, score_with_details.grade);
prop_assert!(score_with_details.details.is_some());
}
#[test]
fn prop_recommendations_for_low_scores(raw_score in 0.0f64..50.0) {
let categories = vec![CategoryScore::new("Critical", raw_score, 100)];
let result = PerfectionScoreResult::new(categories);
prop_assert!(result.recommendations.len() > 0);
prop_assert!(result.recommendations.iter().any(|r| r.contains("critical")));
}
#[test]
fn prop_healthy_for_high_scores(raw_score in 85.0f64..=100.0) {
let categories = vec![CategoryScore::new("Good", raw_score, 100)];
let result = PerfectionScoreResult::new(categories);
prop_assert!(result.recommendations.iter().any(|r| r.contains("healthy")));
}
}
#[test]
fn test_category_score_extreme_values() {
let score = CategoryScore::new("Test", 50.0, 1);
assert_eq!(score.earned_points, 0.5);
let score = CategoryScore::new("Test", 50.0, u16::MAX);
assert!((score.earned_points - (f64::from(u16::MAX) / 2.0)).abs() < 1.0);
}
#[test]
fn test_category_score_floating_point_precision() {
let score = CategoryScore::new("Test", 33.333333, 100);
assert!((score.earned_points - 33.333333).abs() < 0.0001);
}
#[test]
fn test_perfection_score_result_with_negative_target_gap() {
let categories = vec![CategoryScore::new("TDG", 100.0, 100)];
let result = PerfectionScoreResult::new(categories).with_target(50);
assert!(result.target_gap.unwrap() < 0.0);
}
#[test]
fn test_max_perfection_score_constant() {
assert_eq!(MAX_PERFECTION_SCORE, 200);
}
#[test]
fn test_category_score_name_special_characters() {
let score = CategoryScore::new("Test-Category_123", 80.0, 40);
assert_eq!(score.name, "Test-Category_123");
}
#[test]
fn test_category_score_empty_name() {
let score = CategoryScore::new("", 80.0, 40);
assert_eq!(score.name, "");
}
#[test]
fn test_perfection_score_result_many_categories() {
let categories: Vec<CategoryScore> = (0..100)
.map(|i| CategoryScore::new(&format!("Category{}", i), 80.0, 2))
.collect();
let result = PerfectionScoreResult::new(categories);
assert!(
(result.total_score - 160.0).abs() < 0.001,
"Expected ~160.0, got {}",
result.total_score
);
}
#[tokio::test]
async fn test_get_performance_score_workspace_structure() {
let temp_dir = TempDir::new().unwrap();
fs::create_dir_all(temp_dir.path().join("server/benches")).unwrap();
let calc = PerfectionScoreCalculator::new();
let score = calc.get_performance_score(temp_dir.path()).await;
assert_eq!(score, 80.0); }
#[tokio::test]
async fn test_get_mutation_score_server_mutants_toml() {
let temp_dir = TempDir::new().unwrap();
fs::create_dir(temp_dir.path().join("server")).unwrap();
fs::write(temp_dir.path().join("server/mutants.toml"), "[mutants]").unwrap();
let calc = PerfectionScoreCalculator::new();
let score = calc.get_mutation_score(temp_dir.path()).await;
assert_eq!(score, 70.0); }
#[tokio::test]
async fn test_get_coverage_score_workspace_cache() {
let temp_dir = TempDir::new().unwrap();
let metrics_dir = temp_dir.path().join("server/.pmat-metrics");
fs::create_dir_all(&metrics_dir).unwrap();
fs::write(metrics_dir.join("coverage.json"), r#"{"coverage": 92.5}"#).unwrap();
let calc = PerfectionScoreCalculator::new();
let score = calc.get_coverage_score(temp_dir.path()).await;
assert_eq!(score, 92.5);
}
#[tokio::test]
async fn test_get_coverage_score_with_tokio_tests() {
let temp_dir = TempDir::new().unwrap();
fs::create_dir(temp_dir.path().join("src")).unwrap();
fs::write(
temp_dir.path().join("src/lib.rs"),
r#"
#[tokio::test]
async fn test1() {}
#[tokio::test]
async fn test2() {}
"#,
)
.unwrap();
let calc = PerfectionScoreCalculator::new();
let score = calc.get_coverage_score(temp_dir.path()).await;
assert!(score >= 50.0);
}
#[tokio::test]
async fn test_get_documentation_score_partial() {
let temp_dir = TempDir::new().unwrap();
fs::write(temp_dir.path().join("README.md"), "# Test").unwrap();
fs::write(temp_dir.path().join("CHANGELOG.md"), "# Changes").unwrap();
let calc = PerfectionScoreCalculator::new();
let score = calc.get_documentation_score(temp_dir.path()).await;
assert_eq!(score, 60.0); }
#[tokio::test]
async fn test_calculator_fast_mode_mutation_default() {
let temp_dir = TempDir::new().unwrap();
let calc = PerfectionScoreCalculator::new().fast_mode(true);
let result = calc.calculate(temp_dir.path()).await.unwrap();
let mutation_cat = result
.categories
.iter()
.find(|c| c.name == "Mutation Testing")
.unwrap();
assert_eq!(mutation_cat.raw_score, 50.0);
assert!(mutation_cat
.details
.as_ref()
.is_some_and(|d| d.contains("fast mode")));
}
#[test]
fn test_category_weights_copy_trait() {
let weights = CategoryWeights::default();
let copy = weights; assert_eq!(weights.tdg, copy.tdg);
}
#[test]
fn test_category_score_serde_roundtrip() {
let score = CategoryScore::new("Test", 75.5, 40).with_details("Test details");
let json = serde_json::to_string(&score).unwrap();
let deserialized: CategoryScore = serde_json::from_str(&json).unwrap();
assert_eq!(score.name, deserialized.name);
assert_eq!(score.raw_score, deserialized.raw_score);
assert_eq!(score.max_points, deserialized.max_points);
assert_eq!(score.earned_points, deserialized.earned_points);
assert_eq!(score.grade, deserialized.grade);
assert_eq!(score.details, deserialized.details);
}
#[test]
fn test_perfection_score_result_serde_roundtrip() {
let categories = vec![
CategoryScore::new("TDG", 80.0, 40),
CategoryScore::new("Repo", 75.0, 30),
];
let result = PerfectionScoreResult::new(categories).with_target(150);
let json = serde_json::to_string(&result).unwrap();
let deserialized: PerfectionScoreResult = serde_json::from_str(&json).unwrap();
assert_eq!(result.total_score, deserialized.total_score);
assert_eq!(result.max_score, deserialized.max_score);
assert_eq!(result.grade, deserialized.grade);
assert_eq!(result.categories.len(), deserialized.categories.len());
assert_eq!(result.target_gap, deserialized.target_gap);
}
}