use std::fs;
use std::path::Path;
use tempfile::TempDir;
use super::{ComponentLabels, CrateOwnership, cargo_workspace_root, expand_member, package_name};
use crate::core::trusty_tools_config::ResolvedTicketing;
fn crate_at(root: &Path, dir: &str, package: &str) {
let path = root.join(dir);
fs::create_dir_all(&path).unwrap();
fs::write(
path.join("Cargo.toml"),
format!("[package]\nname = \"{package}\"\nversion = \"0.1.0\"\n"),
)
.unwrap();
}
fn workspace() -> TempDir {
let tmp = TempDir::new().unwrap();
let root = tmp.path();
fs::write(
root.join("Cargo.toml"),
"[workspace]\nmembers = [\"crates/*\", \"crates/trusty-audit/ui/src-tauri\"]\n\
resolver = \"2\"\n",
)
.unwrap();
crate_at(root, "crates/trusty-mpm", "trusty-mpm");
crate_at(root, "crates/trusty-audit", "trusty-audit");
crate_at(root, "crates/trusty-console", "trusty-console");
crate_at(root, "crates/trusty-git-analytics", "tga");
crate_at(root, "crates/trusty-review", "trusty_review");
crate_at(root, "crates/trusty-audit/ui/src-tauri", "trusty-audit-ui");
tmp
}
#[test]
fn resolve_accepts_every_workspace_crate_label() {
let tmp = workspace();
let labels = ComponentLabels::resolve(&ResolvedTicketing::default(), Some(tmp.path()));
for expected in [
"trusty-mpm",
"trusty-audit",
"trusty-console",
"tga",
"trusty-audit-ui",
] {
assert!(
labels.accepts(expected),
"expected `{expected}` to be accepted; set was {:?}",
labels.names()
);
}
}
#[test]
fn an_underscored_package_is_accepted_hyphenated() {
let tmp = workspace();
let labels = ComponentLabels::resolve(&ResolvedTicketing::default(), Some(tmp.path()));
assert!(labels.accepts("trusty_review"), "the package name");
assert!(
labels.accepts("trusty-review"),
"the label spelling; set was {:?}",
labels.names()
);
}
#[test]
fn a_nested_member_contributes_no_path_segment() {
let tmp = workspace();
let labels = ComponentLabels::resolve(&ResolvedTicketing::default(), Some(tmp.path()));
assert!(!labels.accepts("src-tauri"), "set was {:?}", labels.names());
assert!(labels.accepts("trusty-audit-ui"), "the package it declares");
}
#[test]
fn resolve_finds_the_workspace_from_a_nested_directory() {
let tmp = workspace();
let nested = tmp.path().join("crates/trusty-mpm");
let labels = ComponentLabels::resolve(&ResolvedTicketing::default(), Some(&nested));
assert!(labels.accepts("trusty-console"));
}
#[test]
fn resolve_without_a_workspace_is_the_seed_table() {
let tmp = TempDir::new().unwrap();
let labels = ComponentLabels::resolve(&ResolvedTicketing::default(), Some(tmp.path()));
assert_eq!(labels.names(), ["trusty-mpm"]);
}
#[test]
fn resolve_with_no_directory_is_the_seed_table() {
let labels = ComponentLabels::resolve(&ResolvedTicketing::default(), None);
assert_eq!(labels.names(), ["trusty-mpm"]);
}
#[test]
fn resolve_ignores_a_malformed_manifest() {
let tmp = TempDir::new().unwrap();
fs::write(tmp.path().join("Cargo.toml"), "[workspace\nmembers = [").unwrap();
let labels = ComponentLabels::resolve(&ResolvedTicketing::default(), Some(tmp.path()));
assert_eq!(labels.names(), ["trusty-mpm"]);
}
#[test]
fn resolve_never_accepts_a_workstream_label() {
let tmp = workspace();
let labels = ComponentLabels::resolve(&ResolvedTicketing::default(), Some(tmp.path()));
assert!(!labels.accepts("ws/trusty-tools-ec"));
}
#[test]
fn from_names_deduplicates() {
let labels = ComponentLabels::from_names(
["a", "b", "a", ""]
.into_iter()
.map(ToString::to_string)
.collect::<Vec<_>>(),
);
assert_eq!(labels.names(), ["a", "b"]);
}
#[test]
fn checked_labels_name_every_workspace_crate() {
let tmp = workspace();
let labels = super::workspace_crate_labels_checked(Some(tmp.path()))
.expect("the fixture is a workspace");
assert_eq!(
labels,
[
"trusty-audit",
"trusty-console",
"tga",
"trusty-mpm",
"trusty-review",
"trusty-audit-ui",
],
"member order, hyphenated, de-duplicated"
);
}
#[test]
fn checked_labels_error_without_a_workspace() {
let tmp = TempDir::new().unwrap();
let err = super::workspace_crate_labels_checked(Some(tmp.path())).unwrap_err();
assert!(
err.contains("no readable Cargo workspace manifest"),
"{err}"
);
}
#[test]
fn checked_labels_error_on_a_malformed_manifest() {
let tmp = TempDir::new().unwrap();
fs::write(tmp.path().join("Cargo.toml"), "[workspace\nmembers = [").unwrap();
let err = super::workspace_crate_labels_checked(Some(tmp.path())).unwrap_err();
assert!(
err.contains("no readable Cargo workspace manifest"),
"{err}"
);
}
#[test]
fn checked_labels_error_on_a_memberless_workspace() {
let tmp = TempDir::new().unwrap();
fs::write(tmp.path().join("Cargo.toml"), "[workspace]\nmembers = []\n").unwrap();
let err = super::workspace_crate_labels_checked(Some(tmp.path())).unwrap_err();
assert!(err.contains("no workspace member"), "{err}");
}
#[test]
fn checked_labels_error_with_no_directory() {
let err = super::workspace_crate_labels_checked(None).unwrap_err();
assert!(err.contains("no working directory"), "{err}");
}
#[test]
fn workspace_root_is_found_from_a_nested_directory() {
let tmp = workspace();
let found = cargo_workspace_root(&tmp.path().join("crates/trusty-audit/ui/src-tauri"))
.expect("the fixture declares a workspace");
assert_eq!(
fs::canonicalize(&found).unwrap(),
fs::canonicalize(tmp.path()).unwrap()
);
}
#[test]
fn no_workspace_root_outside_a_workspace() {
let tmp = TempDir::new().unwrap();
let dir = tmp.path().join("nothing/here");
fs::create_dir_all(&dir).unwrap();
assert_eq!(cargo_workspace_root(&dir), None);
}
#[test]
fn member_glob_expands_one_level() {
let tmp = workspace();
let dirs = expand_member(tmp.path(), "crates/*");
let names: Vec<String> = dirs
.iter()
.filter_map(|d| d.file_name()?.to_str().map(ToString::to_string))
.collect();
assert_eq!(
names,
[
"trusty-audit",
"trusty-console",
"trusty-git-analytics",
"trusty-mpm",
"trusty-review"
]
);
}
#[test]
fn member_literal_path_is_kept() {
let tmp = workspace();
let dirs = expand_member(tmp.path(), "crates/trusty-audit/ui/src-tauri");
assert_eq!(dirs, [tmp.path().join("crates/trusty-audit/ui/src-tauri")]);
}
#[test]
fn member_pattern_that_matches_nothing_yields_nothing() {
let tmp = workspace();
assert!(expand_member(tmp.path(), "services/*").is_empty());
}
#[test]
fn resolve_accepts_every_live_workspace_crate() {
let root = &trusty_common::test_harness::test_repo_root().expect("resolve the checkout");
let labels = ComponentLabels::resolve(&ResolvedTicketing::default(), Some(root));
let crates_dir = root.join("crates");
let entries = fs::read_dir(&crates_dir)
.unwrap_or_else(|e| panic!("read_dir({}): {e}", crates_dir.display()));
for entry in entries {
let dir = entry.unwrap().path();
if !dir.is_dir() {
continue;
}
let Some(name) = package_name(&dir) else {
continue;
};
assert!(
labels.accepts(&name),
"crates/{} declares package `{name}`, not accepted; set was {:?}",
dir.file_name().unwrap().to_string_lossy(),
labels.names()
);
}
}
fn ownership() -> CrateOwnership {
CrateOwnership::from_members([
("crates/trusty-mpm/".to_string(), "trusty-mpm".to_string()),
(
"crates/trusty-audit/".to_string(),
"trusty-audit".to_string(),
),
(
"crates/trusty-audit/ui/src-tauri/".to_string(),
"trusty-audit-ui".to_string(),
),
(
"crates/trusty-mpm-ext/".to_string(),
"trusty-mpm-ext".to_string(),
),
])
}
#[test]
fn ownership_labels_a_path_under_a_member() {
let map = CrateOwnership::resolve(Some(workspace().path()));
assert_eq!(
map.labels_for_paths(&["crates/trusty-mpm/src/lib.rs"]),
vec!["trusty-mpm".to_string()]
);
assert_eq!(
map.labels_for_paths(&["crates/trusty-git-analytics/src/main.rs"]),
vec!["tga".to_string()]
);
assert_eq!(
map.labels_for_paths(&["crates/trusty-review/src/lib.rs"]),
vec!["trusty-review".to_string()],
"a package spelled `trusty_review` is labelled with a hyphen"
);
}
#[test]
fn ownership_prefers_the_longest_matching_member() {
assert_eq!(
ownership().labels_for_paths(&["crates/trusty-audit/ui/src-tauri/src/main.rs"]),
vec!["trusty-audit-ui".to_string()]
);
}
#[test]
fn ownership_does_not_leak_across_a_sibling_prefix() {
assert_eq!(
ownership().labels_for_paths(&["crates/trusty-mpm-ext/src/main.rs"]),
vec!["trusty-mpm-ext".to_string()],
"a sibling whose name extends another member's earns only its own label"
);
assert_eq!(
ownership().labels_for_paths(&["crates/trusty-mpm/src/lib.rs"]),
vec!["trusty-mpm".to_string()],
"and the shorter sibling is unaffected"
);
let mpm_only = CrateOwnership::from_members([(
"crates/trusty-mpm/".to_string(),
"trusty-mpm".to_string(),
)]);
assert!(
mpm_only
.labels_for_paths(&["crates/trusty-mpm-ext/src/main.rs"])
.is_empty(),
"a member directory prefixes a path only at a directory boundary"
);
}
#[test]
fn ownership_ignores_a_path_no_crate_owns() {
assert!(
ownership()
.labels_for_paths(&["docs/specs/DOC-65.md", ".github/workflows/ci.yml"])
.is_empty(),
"a docs-or-CI-only diff earns no component label"
);
}
#[test]
fn ownership_deduplicates_multi_file_crates() {
let labels = ownership().labels_for_paths(&[
"crates/trusty-mpm/src/a.rs",
"crates/trusty-mpm/src/b.rs",
"./crates/trusty-audit/src/c.rs",
"README.md",
]);
assert_eq!(
labels,
vec!["trusty-mpm".to_string(), "trusty-audit".to_string()],
"one label per crate, in first-seen order, `./` prefix tolerated"
);
}
#[test]
fn ownership_without_a_workspace_is_empty() {
let tmp = TempDir::new().unwrap();
assert!(
CrateOwnership::resolve(Some(tmp.path()))
.labels_for_paths(&["crates/trusty-mpm/src/lib.rs"])
.is_empty()
);
assert!(
CrateOwnership::resolve(None)
.labels_for_paths(&["crates/trusty-mpm/src/lib.rs"])
.is_empty()
);
}