use codelore_lib::Options;
use codelore_lib::analyses::coordination_needs::run_coordination_needs;
use codelore_lib::facts::FactsDb;
use codelore_lib::repo::gix_repo::GixRepo;
fn ingest(repo_path: &std::path::Path) -> FactsDb {
let db = FactsDb::new_in_memory().expect("in-memory db");
let repo = GixRepo::open(repo_path).expect("open repo");
let opts = Options {
repo_path: repo_path.to_path_buf(),
min_revs: 1,
min_shared_revs: 1,
..Options::default()
};
db.ingest(&repo, &opts).expect("ingest");
db
}
#[test]
fn tiny_repo_all_tiers_single() {
let fixture = codelore_lib::test_support::tiny_repo::build();
let db = ingest(fixture.dir.path());
let opts = Options {
repo_path: fixture.dir.path().to_path_buf(),
min_revs: 1,
window_days: 365,
..Options::default()
};
let rows = run_coordination_needs(&db, &opts).expect("run coordination-needs");
for row in &rows {
assert_eq!(
row.tier, "single",
"tiny_repo is single-author; every file must carry tier='single', got {row:?}",
);
assert!(
row.fragmentation.abs() < f64::EPSILON,
"single-author file must have fragmentation=0"
);
}
}
#[test]
fn coupling_repo_entropy_positive_for_coupled_files() {
let fixture = codelore_lib::test_support::coupling_repo::build();
let db = ingest(fixture.dir.path());
let opts = Options {
repo_path: fixture.dir.path().to_path_buf(),
min_revs: 1,
window_days: 365,
..Options::default()
};
let rows = run_coordination_needs(&db, &opts).expect("run coordination-needs");
assert!(
!rows.is_empty(),
"coupling_repo must produce at least one row"
);
for row in &rows {
assert_eq!(
row.tier, "single",
"coupling_repo is single-author; tier must be 'single' for {:?}",
row.path
);
}
let alpha_svc = rows
.iter()
.find(|r| r.path == "src/alpha/svc.rs")
.expect("src/alpha/svc.rs must be present");
let beta_svc = rows
.iter()
.find(|r| r.path == "src/beta/svc.rs")
.expect("src/beta/svc.rs must be present");
assert!(
alpha_svc.cochange_entropy > 0.0,
"src/alpha/svc.rs co-changes with beta/svc.rs; entropy must be > 0, got {}",
alpha_svc.cochange_entropy
);
assert!(
beta_svc.cochange_entropy > 0.0,
"src/beta/svc.rs co-changes with alpha/svc.rs; entropy must be > 0, got {}",
beta_svc.cochange_entropy
);
for row in &rows {
assert!(
row.cochange_entropy >= 0.0,
"co-change entropy must be non-negative for {:?}, got {}",
row.path,
row.cochange_entropy,
);
}
let alpha_util = rows
.iter()
.find(|r| r.path == "src/alpha/util.rs")
.expect("src/alpha/util.rs must be present");
assert!(
(alpha_util.interleave - 0.0_f64).abs() < 1e-9,
"src/alpha/util.rs: 3 commits by same author → 0 switches → interleave=0.0, got {}",
alpha_util.interleave
);
assert_eq!(
alpha_util.total_commits, 3,
"total_commits must disclose the interleave denominator's n, got {}",
alpha_util.total_commits
);
}
#[test]
fn delivery_repo_fragmentation_and_nonsingle_tier() {
let fixture = codelore_lib::test_support::delivery_repo::build();
let db = ingest(fixture.dir.path());
let opts = Options {
repo_path: fixture.dir.path().to_path_buf(),
min_revs: 1,
window_days: 365,
..Options::default()
};
let rows = run_coordination_needs(&db, &opts).expect("run coordination-needs");
assert!(
!rows.is_empty(),
"delivery_repo must produce at least one coordination-needs row"
);
let multi_author = rows.iter().find(|r| r.authors > 1);
assert!(
multi_author.is_some(),
"delivery_repo has 3 authors; at least one file must show authors > 1"
);
let multi = multi_author.unwrap();
assert!(
multi.fragmentation > 0.0,
"multi-author file must have fragmentation > 0, got {}",
multi.fragmentation
);
let non_single = rows.iter().any(|r| r.tier != "single");
assert!(
non_single,
"delivery_repo has multi-author files; at least one row must have tier != 'single'"
);
let rework = rows
.iter()
.find(|r| r.path == "src/rework.rs")
.expect("src/rework.rs must appear: 3 commits, min_revs=1, Rust tier-1");
assert!(
(rework.interleave - 0.5_f64).abs() < 1e-9,
"src/rework.rs: [Alice, Alice, Bob] → 1 switch / 2 intervals = 0.5, got {}",
rework.interleave
);
assert_eq!(
rework.total_commits, 3,
"src/rework.rs: 3 commits total, got {}",
rework.total_commits
);
}
#[test]
fn binary_only_path_all_zero_loc_added_does_not_crash_and_has_zero_fragmentation() {
let db = FactsDb::new_in_memory().expect("in-memory db");
db.execute_batch(
"INSERT INTO author_aliases (raw_name, raw_email, canonical, is_bot) \
VALUES ('Alice', 'alice@x.com', 'Alice', false)",
)
.expect("insert author_aliases");
db.execute_batch(
"INSERT INTO commits (rev, author_email, author_name, \
committer_email, canonical_author, date, committer_date, \
message, is_merge, parent_count) VALUES \
('c1', 'alice@x.com', 'Alice', 'alice@x.com', 'Alice', \
TIMESTAMP '2026-01-01', TIMESTAMP '2026-01-01', 'add binary', false, 1)",
)
.expect("insert commits");
db.execute_batch(
"INSERT INTO changes (rev, path, change_type, loc_added, loc_deleted) \
VALUES ('c1', 'assets/logo.png', 'binary', 0, 0)",
)
.expect("insert changes");
let repo_dir = tempfile::tempdir().expect("tempdir");
let opts = Options {
repo_path: repo_dir.path().to_path_buf(),
min_revs: 1,
window_days: 90,
..Options::default()
};
let rows = run_coordination_needs(&db, &opts)
.expect("all-zero loc_added path must not crash run_coordination_needs");
let row = rows
.iter()
.find(|r| r.path == "assets/logo.png")
.expect("assets/logo.png must appear in results");
assert!(
row.fragmentation.abs() < f64::EPSILON,
"all-zero-loc_added path must have fragmentation=0.0 (degenerate-knowledge \
default), got {}",
row.fragmentation
);
}
#[test]
fn dormant_multi_author_file_is_not_classified_high() {
let db = FactsDb::new_in_memory().expect("in-memory db");
db.execute_batch(
"INSERT INTO author_aliases (raw_name, raw_email, canonical, is_bot) VALUES \
('Alice', 'alice@x.com', 'Alice', false), \
('Bob', 'bob@x.com', 'Bob', false)",
)
.expect("insert author_aliases");
db.execute_batch(
"INSERT INTO commits (rev, author_email, author_name, \
committer_email, canonical_author, date, committer_date, \
message, is_merge, parent_count) VALUES \
('c1', 'alice@x.com', 'Alice', 'alice@x.com', 'Alice', \
TIMESTAMP '2025-06-01', TIMESTAMP '2025-06-01', 'alice edit', false, 1), \
('c2', 'bob@x.com', 'Bob', 'bob@x.com', 'Bob', \
TIMESTAMP '2025-06-01', TIMESTAMP '2025-06-01', 'bob edit', false, 1), \
('c3', 'alice@x.com', 'Alice', 'alice@x.com', 'Alice', \
TIMESTAMP '2026-01-01', TIMESTAMP '2026-01-01', 'recent unrelated', false, 1)",
)
.expect("insert commits");
db.execute_batch(
"INSERT INTO changes (rev, path, change_type, loc_added, loc_deleted) VALUES \
('c1', 'dormant.rs', 'added', 100, 0), \
('c2', 'dormant.rs', 'modified', 100, 0), \
('c3', 'other.rs', 'added', 10, 0)",
)
.expect("insert changes");
let repo_dir = tempfile::tempdir().expect("tempdir");
let opts = Options {
repo_path: repo_dir.path().to_path_buf(),
min_revs: 1,
window_days: 90,
..Options::default()
};
let rows = run_coordination_needs(&db, &opts).expect("run coordination-needs");
let dormant = rows
.iter()
.find(|r| r.path == "dormant.rs")
.expect("dormant.rs must appear in results");
assert_eq!(
dormant.authors, 0,
"dormant.rs has no commits inside the trailing window; authors must be 0, got {dormant:?}"
);
assert!(
dormant.fragmentation >= 0.50,
"dormant.rs historical fragmentation must be >= 0.50 for this to be a \
meaningful regression test, got {}",
dormant.fragmentation
);
assert!(
dormant.interleave >= 0.50,
"dormant.rs historical interleave must be >= 0.50 for this to be a \
meaningful regression test, got {}",
dormant.interleave
);
assert_eq!(
dormant.tier, "single",
"a dormant file (authors=0, no CURRENT coordination activity) must fold into \
the 'single' tier, not be misclassified 'high' from stale historical signal, \
got {dormant:?}"
);
assert_eq!(
dormant.total_commits, 2,
"dormant.rs has exactly 2 historical commits (c1, c2) — total_commits must \
disclose that thin sample, got {}",
dormant.total_commits
);
}
#[test]
fn equal_fragmentation_rows_are_ordered_deterministically_by_entity() {
let db = FactsDb::new_in_memory().expect("in-memory db");
db.execute_batch(
"INSERT INTO author_aliases (raw_name, raw_email, canonical, is_bot) \
VALUES ('Solo', 'solo@x.com', 'Solo', false)",
)
.expect("insert author_aliases");
db.execute_batch(
"INSERT INTO commits (rev, author_email, author_name, \
committer_email, canonical_author, date, committer_date, \
message, is_merge, parent_count) VALUES \
('c1', 'solo@x.com', 'Solo', 'solo@x.com', 'Solo', \
TIMESTAMP '2026-01-01', TIMESTAMP '2026-01-01', 'z', false, 1), \
('c2', 'solo@x.com', 'Solo', 'solo@x.com', 'Solo', \
TIMESTAMP '2026-01-02', TIMESTAMP '2026-01-02', 'm', false, 1), \
('c3', 'solo@x.com', 'Solo', 'solo@x.com', 'Solo', \
TIMESTAMP '2026-01-03', TIMESTAMP '2026-01-03', 'a', false, 1)",
)
.expect("insert commits");
db.execute_batch(
"INSERT INTO changes (rev, path, change_type, loc_added, loc_deleted) VALUES \
('c1', 'z_file.rs', 'added', 10, 0), \
('c2', 'm_file.rs', 'added', 10, 0), \
('c3', 'a_file.rs', 'added', 10, 0)",
)
.expect("insert changes");
let repo_dir = tempfile::tempdir().expect("tempdir");
let opts = Options {
repo_path: repo_dir.path().to_path_buf(),
min_revs: 1,
window_days: 90,
..Options::default()
};
let rows = run_coordination_needs(&db, &opts).expect("run coordination-needs");
assert_eq!(
rows.len(),
3,
"expected exactly the 3 fixture paths, got {rows:?}"
);
for row in &rows {
assert!(
row.fragmentation.abs() < f64::EPSILON,
"single-author file must have fragmentation=0, got {row:?}"
);
}
let paths: Vec<&str> = rows.iter().map(|r| r.path.as_str()).collect();
assert_eq!(
paths,
vec!["a_file.rs", "m_file.rs", "z_file.rs"],
"tied fragmentation rows must be ordered deterministically by entity ASC"
);
}
#[test]
fn coordination_needs_output_is_run_to_run_identical() {
let fixture = codelore_lib::test_support::coupling_repo::build();
let db = ingest(fixture.dir.path());
let opts = Options {
repo_path: fixture.dir.path().to_path_buf(),
min_revs: 1,
window_days: 365,
..Options::default()
};
let baseline = format!(
"{:?}",
run_coordination_needs(&db, &opts).expect("run coordination-needs")
);
assert!(
baseline.contains("cochange_entropy"),
"fixture must exercise the entropy term"
);
for i in 0..8 {
let again = format!(
"{:?}",
run_coordination_needs(&db, &opts).expect("re-run coordination-needs")
);
assert_eq!(
again, baseline,
"coordination-needs output must be identical across runs (iteration {i})"
);
}
}