use codelore_lib::Options;
use codelore_lib::analyses::pair_programming::run_pair_programming;
use codelore_lib::facts::FactsDb;
use codelore_lib::repo::GixRepo;
#[test]
fn pair_programming_executes_cleanly_on_tiny_repo() {
let tiny = codelore_lib::test_support::tiny_repo::build();
let repo = GixRepo::open(tiny.dir.path()).expect("open");
let db = FactsDb::new_in_memory().expect("db");
let opts = Options {
repo_path: tiny.dir.path().to_path_buf(),
..Options::default()
};
db.ingest(&repo, &opts).expect("ingest");
let rows = run_pair_programming(&db, &opts).expect("run pair-programming");
assert!(
rows.is_empty(),
"tiny_repo has no Co-Authored-By: trailers — expected empty pair-programming result, got {} rows",
rows.len(),
);
}
#[test]
fn pair_programming_surfaces_real_pairs_from_co_authored_trailers() {
use std::path::Path;
use std::process::Command;
let dir = tempfile::tempdir().unwrap();
let path = dir.path();
let git = |args: &[&str]| {
let s = Command::new("git")
.arg("-C")
.arg(path)
.args(args)
.status()
.expect("git");
assert!(s.success(), "git {args:?} failed");
};
let write = |rel: &str, contents: &str| {
let full = path.join(rel);
if let Some(parent) = full.parent() {
std::fs::create_dir_all(parent).unwrap();
}
std::fs::write(full, contents).unwrap();
};
git(&["init", "-b", "main", "--quiet"]);
git(&["config", "user.email", "primary@example.com"]);
git(&["config", "user.name", "Primary Dev"]);
write("README.md", "hello\n");
git(&["add", "."]);
git(&[
"commit",
"-m",
"first\n\nCo-Authored-By: Pair Dev <pair@example.com>",
"--quiet",
]);
let repo = GixRepo::open(path as &Path).expect("gix open");
let db = FactsDb::new_in_memory().expect("db");
let opts = Options {
repo_path: path.to_path_buf(),
min_shared_revs: 1,
..Options::default()
};
db.ingest(&repo, &opts).expect("ingest");
let rows = run_pair_programming(&db, &opts).expect("run pair-programming");
assert_eq!(rows.len(), 1, "expected exactly one pair row, got {rows:?}");
assert_eq!(rows[0].pair_commits, 1, "expected pair_commits=1");
let pair: Vec<&str> = vec![rows[0].author_a.as_str(), rows[0].author_b.as_str()];
assert!(
pair.contains(&"primary@example.com"),
"primary author missing from pair: {pair:?}",
);
assert!(
pair.contains(&"pair@example.com"),
"co-authored author missing from pair: {pair:?}",
);
}
#[test]
fn pair_programming_dedupes_pair_regardless_of_primary_orientation() {
use std::process::Command;
let dir = tempfile::tempdir().unwrap();
let path = dir.path();
let git = |args: &[&str]| {
let s = Command::new("git")
.arg("-C")
.arg(path)
.args(args)
.status()
.expect("git");
assert!(s.success(), "git {args:?} failed");
};
let write = |rel: &str, contents: &str| {
std::fs::write(path.join(rel), contents).unwrap();
};
git(&["init", "-b", "main", "--quiet"]);
git(&["config", "user.email", "bob@example.com"]);
git(&["config", "user.name", "Bob"]);
write("a.txt", "v1\n");
git(&["add", "."]);
git(&[
"commit",
"-m",
"c1\n\nCo-Authored-By: Alice <alice@example.com>",
"--quiet",
]);
git(&["config", "user.email", "alice@example.com"]);
git(&["config", "user.name", "Alice"]);
write("a.txt", "v2\n");
git(&["add", "."]);
git(&[
"commit",
"-m",
"c2\n\nCo-Authored-By: Bob <bob@example.com>",
"--quiet",
]);
let repo = GixRepo::open(path).expect("gix open");
let db = FactsDb::new_in_memory().expect("db");
let opts = Options {
repo_path: path.to_path_buf(),
min_shared_revs: 1,
..Options::default()
};
db.ingest(&repo, &opts).expect("ingest");
let rows = run_pair_programming(&db, &opts).expect("run pair-programming");
assert_eq!(
rows.len(),
1,
"alice↔bob encountered in two orientations must dedup to 1 pair row; got {rows:?}",
);
assert_eq!(
rows[0].pair_commits, 2,
"both commits should count toward the same pair (got {:?})",
rows[0],
);
assert_eq!(rows[0].author_a, "alice@example.com");
assert_eq!(rows[0].author_b, "bob@example.com");
}