use super::*;
use crate::snapshot::*;
use chrono::{Duration, Utc};
use std::path::PathBuf;
#[test]
fn compute_team_small_team_metrics_unscored() {
let mut snapshot = RepoSnapshot::new(
PathBuf::from("/tmp"),
"test".into(),
"main".into(),
TimeWindow::default(),
);
for i in 0..3 {
snapshot.authors.push(Author {
id: i,
name: format!("Author {i}"),
email: format!("a{i}@t.com"),
});
}
let result = compute_team(&snapshot, &crate::config::TeamThresholds::default());
assert_eq!(result.score, 100);
assert!(result.metrics.iter().all(|m| m.score.is_none()));
assert!(result
.metrics
.iter()
.all(|m| m.description.contains("not applicable")));
}
fn make_solo_snapshot() -> RepoSnapshot {
let mut snapshot = RepoSnapshot::new(
PathBuf::from("/tmp"),
"test".into(),
"main".into(),
TimeWindow::default(),
);
snapshot.authors = vec![Author {
id: 0,
name: "Alice".into(),
email: "a@t.com".into(),
}];
snapshot
}
#[test]
fn knowledge_distribution_solo_project_has_no_score() {
let snapshot = make_solo_snapshot();
let result = knowledge_distribution(&snapshot, &crate::config::TeamThresholds::default());
assert_eq!(result.score, None);
assert!(result.description.contains("Solo project"));
}
#[test]
fn ownership_clarity_solo_project_has_no_score() {
let snapshot = make_solo_snapshot();
let result = ownership_clarity(&snapshot, &crate::config::TeamThresholds::default());
assert_eq!(result.score, None);
assert!(result.description.contains("Solo project"));
}
#[test]
fn collaboration_patterns_solo_project_has_no_score() {
let snapshot = make_solo_snapshot();
let result = collaboration_patterns(&snapshot, &crate::config::TeamThresholds::default());
assert_eq!(result.score, None);
assert!(result.description.contains("Solo project"));
}
#[test]
fn knowledge_distribution_detects_concentration() {
let mut snapshot = RepoSnapshot::new(
PathBuf::from("/tmp"),
"test".into(),
"main".into(),
TimeWindow::default(),
);
snapshot.authors = vec![
Author {
id: 0,
name: "Alice".into(),
email: "a@t.com".into(),
},
Author {
id: 1,
name: "Bob".into(),
email: "b@t.com".into(),
},
Author {
id: 2,
name: "Carol".into(),
email: "c@t.com".into(),
},
];
let now = Utc::now();
let mut blame = Vec::new();
for _ in 0..95 {
blame.push(BlameLine::new(0, now));
}
for _ in 0..4 {
blame.push(BlameLine::new(1, now));
}
for _ in 0..1 {
blame.push(BlameLine::new(2, now));
}
snapshot.blame_map.insert(PathBuf::from("file.rs"), blame);
let result = knowledge_distribution(&snapshot, &crate::config::TeamThresholds::default());
match result.raw_value {
RawValue::Float(gini) => assert!(gini >= 0.5, "Expected Gini >= 0.5, got {}", gini),
_ => panic!("Expected Float"),
}
assert!(result.score.unwrap() <= 50);
}
#[test]
fn contributor_activity_counts_active() {
let mut snapshot = RepoSnapshot::new(
PathBuf::from("/tmp"),
"test".into(),
"main".into(),
TimeWindow::default(),
);
snapshot.authors = vec![
Author {
id: 0,
name: "Alice".into(),
email: "a@t.com".into(),
},
Author {
id: 1,
name: "Bob".into(),
email: "b@t.com".into(),
},
Author {
id: 2,
name: "Carol".into(),
email: "c@t.com".into(),
},
Author {
id: 3,
name: "Dave".into(),
email: "d@t.com".into(),
},
Author {
id: 4,
name: "Eve".into(),
email: "e@t.com".into(),
},
];
let now = Utc::now();
for i in 0..3 {
snapshot.commits.push(Commit {
id: CommitId(i as u32),
author: i,
timestamp: now - Duration::days(10),
message: "msg".into(),
files_changed: vec![],
is_merge: false,
parent_count: 1,
});
}
snapshot.build_indexes();
let result = contributor_activity(&snapshot, &crate::config::TeamThresholds::default());
match result.raw_value {
RawValue::Percentage(p) => assert!((p - 60.0).abs() < 1.0, "Expected ~60%, got {}", p),
_ => panic!("Expected Percentage"),
}
}
#[test]
fn ownership_clarity_detects_owners() {
let mut snapshot = RepoSnapshot::new(
PathBuf::from("/tmp"),
"test".into(),
"main".into(),
TimeWindow::default(),
);
snapshot.authors = vec![
Author {
id: 0,
name: "Alice".into(),
email: "a@t.com".into(),
},
Author {
id: 1,
name: "Bob".into(),
email: "b@t.com".into(),
},
];
let now = Utc::now();
let mut blame1 = Vec::new();
for _ in 0..80 {
blame1.push(BlameLine::new(0, now));
}
for _ in 0..20 {
blame1.push(BlameLine::new(1, now));
}
snapshot.blame_map.insert(PathBuf::from("f1.rs"), blame1);
let mut blame2 = Vec::new();
for _ in 0..50 {
blame2.push(BlameLine::new(0, now));
}
for _ in 0..50 {
blame2.push(BlameLine::new(1, now));
}
snapshot.blame_map.insert(PathBuf::from("f2.rs"), blame2);
let result = ownership_clarity(&snapshot, &crate::config::TeamThresholds::default());
match result.raw_value {
RawValue::Percentage(p) => assert!((p - 50.0).abs() < 1.0),
_ => panic!("Expected Percentage"),
}
}
#[test]
fn collaboration_detects_silos() {
let mut snapshot = RepoSnapshot::new(
PathBuf::from("/tmp"),
"test".into(),
"main".into(),
TimeWindow::default(),
);
snapshot.authors = vec![
Author {
id: 0,
name: "Alice".into(),
email: "a@t.com".into(),
},
Author {
id: 1,
name: "Bob".into(),
email: "b@t.com".into(),
},
];
let now = Utc::now();
let mut blame_auth = Vec::new();
for _ in 0..100 {
blame_auth.push(BlameLine::new(0, now));
}
snapshot
.blame_map
.insert(PathBuf::from("auth/login.rs"), blame_auth);
let mut blame_api = Vec::new();
for _ in 0..60 {
blame_api.push(BlameLine::new(0, now));
}
for _ in 0..40 {
blame_api.push(BlameLine::new(1, now));
}
snapshot
.blame_map
.insert(PathBuf::from("api/routes.rs"), blame_api);
let result = collaboration_patterns(&snapshot, &crate::config::TeamThresholds::default());
match result.raw_value {
RawValue::Count(c) => assert_eq!(c, 1, "Should detect 1 silo (auth)"),
_ => panic!("Expected Count"),
}
}
#[test]
fn merge_patterns_counts_merges() {
let mut snapshot = RepoSnapshot::new(
PathBuf::from("/tmp"),
"test".into(),
"main".into(),
TimeWindow::default(),
);
let now = Utc::now();
for i in 0..20 {
snapshot.commits.push(Commit {
id: CommitId(i as u32),
author: 0,
timestamp: now - Duration::hours(i * 24),
message: "msg".into(),
files_changed: vec![],
is_merge: i < 5, parent_count: if i < 5 { 2 } else { 1 },
});
}
let result = merge_patterns(&snapshot, &crate::config::TeamThresholds::default());
match result.raw_value {
RawValue::Count(c) => assert_eq!(c, 5),
_ => panic!("Expected Count"),
}
}