use std::path::Path;
use fallow_cli::codeowners::CodeOwners;
fn write(path: &Path, contents: &str) {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).expect("create parent directories");
}
std::fs::write(path, contents).expect("write file");
}
#[test]
fn gitlab_codeowners_reproduction_from_issue_127() {
let dir = tempfile::tempdir().expect("create temp dir");
let codeowners = "\
# Default section (no header, rules before first section)
* @default-owner
[Utilities] @utils-team
src/utils/
[UI Components] @ui-team
src/components/
";
write(&dir.path().join(".gitlab/CODEOWNERS"), codeowners);
let co = CodeOwners::discover(dir.path()).expect("discover succeeds");
assert_eq!(co.owner_of(Path::new("README.md")), Some("@default-owner"));
assert_eq!(
co.owner_of(Path::new("src/utils/greet.ts")),
Some("@utils-team")
);
assert_eq!(
co.owner_of(Path::new("src/components/button.ts")),
Some("@ui-team")
);
}
#[test]
fn gitlab_codeowners_probed_at_root() {
let dir = tempfile::tempdir().expect("create temp dir");
write(
&dir.path().join("CODEOWNERS"),
"[Section] @root-team\nsrc/\n",
);
write(
&dir.path().join(".gitlab/CODEOWNERS"),
"* @should-not-be-used\n",
);
let co = CodeOwners::discover(dir.path()).expect("discover succeeds");
assert_eq!(co.owner_of(Path::new("src/lib.ts")), Some("@root-team"));
}
#[test]
fn gitlab_exclusion_pattern_clears_ownership_end_to_end() {
let dir = tempfile::tempdir().expect("create temp dir");
let codeowners = "\
* @default
!src/vendor/
";
write(&dir.path().join(".github/CODEOWNERS"), codeowners);
let co = CodeOwners::discover(dir.path()).expect("discover succeeds");
assert_eq!(co.owner_of(Path::new("README.md")), Some("@default"));
assert_eq!(co.owner_of(Path::new("src/vendor/lib.js")), None);
}
#[test]
fn discover_returns_err_for_repo_without_codeowners() {
let dir = tempfile::tempdir().expect("create temp dir");
let err = CodeOwners::discover(dir.path()).expect_err("no CODEOWNERS file");
assert!(
err.contains("no CODEOWNERS file found"),
"unexpected error: {err}"
);
}
#[test]
fn load_with_explicit_path_bypasses_probe() {
let dir = tempfile::tempdir().expect("create temp dir");
write(
&dir.path().join("custom/OWNERS"),
"[Team A] @team-a\nsrc/\n",
);
let co = CodeOwners::load(dir.path(), Some("custom/OWNERS")).expect("load with explicit path");
assert_eq!(co.owner_of(Path::new("src/a.ts")), Some("@team-a"));
}
#[test]
fn from_file_surfaces_parse_error_for_malformed_glob() {
let dir = tempfile::tempdir().expect("create temp dir");
let path = dir.path().join(".github/CODEOWNERS");
write(&path, "foo[unclosed @owner\n");
let err = CodeOwners::from_file(&path).expect_err("parse should fail");
assert!(
err.contains("invalid CODEOWNERS pattern"),
"unexpected error: {err}"
);
}