use codelore_lib::analyses::dashboard::{
run_clone_summary, run_daily_commits, run_imports_for_arch_graph, run_kamei_risk, run_trends,
run_xray,
};
use codelore_lib::facts::FactsDb;
use codelore_lib::repo::GixRepo;
use codelore_lib::test_support::permissive_coupling_opts;
#[test]
fn dashboard_queries_run_over_ingested_fixture() {
let fixture = codelore_lib::test_support::biomarker_repo::build();
let repo = GixRepo::open(fixture.dir.path()).expect("open");
let db = FactsDb::new_in_memory().expect("db");
let opts = permissive_coupling_opts(fixture.dir.path().to_path_buf());
db.ingest(&repo, &opts).expect("ingest");
let daily = run_daily_commits(&db).expect("run_daily_commits");
assert!(!daily.is_empty(), "fixture has commits → daily rows");
for d in &daily {
assert!(!d.date.is_empty(), "daily row has a date");
assert!(d.count >= 1, "an active day has ≥1 commit, got {}", d.count);
}
let xray = run_xray(&db, 100).expect("run_xray");
assert!(
!xray.is_empty(),
"the fixture has functions; an empty x-ray passes the shape loop vacuously"
);
for x in &xray {
assert!(!x.function.is_empty(), "xray entry has a function name");
assert!(
x.start_line <= x.end_line,
"xray line range {}..{} inverted for {}",
x.start_line,
x.end_line,
x.path
);
}
let clones = run_clone_summary(&db).expect("run_clone_summary");
assert!(
!clones.is_empty(),
"the fixture carries clone pairs; an empty summary passes the loop vacuously"
);
for c in &clones {
assert!(c.groups >= 1, "clone-summary groups ≥1, got {}", c.groups);
}
let mut paths: Vec<String> = xray.iter().map(|x| x.path.clone()).collect();
paths.sort();
paths.dedup();
let trends = run_trends(&db, &opts, &paths).expect("run_trends");
assert!(
!trends.is_empty(),
"committed fixture paths must produce monthly trend points"
);
for t in &trends {
assert!(!t.month.is_empty(), "trend point has a month");
}
let _kamei = run_kamei_risk(&db, 100).expect("run_kamei_risk");
let _imports = run_imports_for_arch_graph(&db).expect("run_imports_for_arch_graph");
}
#[test]
fn trends_sparkline_covers_pre_rename_history_under_lineage() {
use codelore_lib::Options;
use codelore_lib::analyses::revisions::run_revisions;
let fixture = codelore_lib::test_support::differential_repo::build();
let repo = GixRepo::open(fixture.dir.path()).expect("open");
let opts_on = Options {
repo_path: fixture.dir.path().to_path_buf(),
min_revs: 1,
use_canonical_lineage: true,
..Options::default()
};
let db_on = FactsDb::new_in_memory().expect("db");
db_on.ingest(&repo, &opts_on).expect("ingest");
let head_path = run_revisions(&db_on, &opts_on)
.expect("revisions")
.into_iter()
.find(|(p, _)| p.contains("new_name"))
.map(|(p, _)| p)
.expect("fixture has a renamed new_name path");
let paths = vec![head_path];
let on_sum: f64 = run_trends(&db_on, &opts_on, &paths)
.expect("trends lineage-on")
.iter()
.map(|t| t.hotspot_score)
.sum();
let opts_off = Options {
use_canonical_lineage: false,
..opts_on.clone()
};
let db_off = FactsDb::new_in_memory().expect("db");
db_off.ingest(&repo, &opts_off).expect("ingest");
let off_sum: f64 = run_trends(&db_off, &opts_off, &paths)
.expect("trends lineage-off")
.iter()
.map(|t| t.hotspot_score)
.sum();
assert!(
on_sum > off_sum,
"lineage must fold pre-rename revisions into the head-path sparkline: on={on_sum} off={off_sum}"
);
assert!(
on_sum >= 2.0,
"head path should aggregate ≥2 revisions (pre-rename + post-rename); got {on_sum}"
);
}
#[test]
fn same_second_ties_resolve_to_the_newer_commit_in_newest_first_queries() {
let db = FactsDb::new_in_memory().expect("db");
let commit = |rev: &str| {
format!(
"INSERT INTO commits (rev, author_email, author_name, committer_email, \
canonical_author, date, committer_date, message, is_merge, parent_count, \
la, ld, nf, nd, ndev, nuc, exp, entropy, fix) \
VALUES ('{rev}', 'a@x', 'A', 'a@x', 'a@x', \
'2026-03-05 12:00:00', '2026-03-05 12:00:00', 'm', false, 1, \
1, 0, 1, 1, 1, 1, 1, 0.0, false)"
)
};
for stmt in [
commit("aaa_newer"),
commit("zzz_older"),
"INSERT INTO changes VALUES ('aaa_newer', 'f.rs', 'modified', NULL, 2, 1)".to_string(),
"INSERT INTO changes VALUES ('zzz_older', 'f.rs', 'added', NULL, 5, 0)".to_string(),
] {
db.execute_batch(&stmt).expect("seed");
}
let kamei = run_kamei_risk(&db, 1).expect("kamei");
assert_eq!(
kamei[0].rev, "aaa_newer",
"kamei sparkline's last-N must start at the NEWER of a same-second pair"
);
let opts = codelore_lib::Options {
min_revs: 1,
..codelore_lib::Options::default()
};
let evidence = codelore_lib::quality_gates::evidence::evidence_for_path(&db, &opts, "f.rs", 5)
.expect("evidence");
let revs: Vec<&str> = evidence.iter().map(|e| e.rev.as_str()).collect();
assert_eq!(
revs,
vec!["aaa_newer", "zzz_older"],
"evidence chain must list the newer commit first at a same-second tie"
);
}