use super::*;
use std::path::Path;
fn init_git_work_tree(base_dir: &Path) -> Option<()> {
let status = crate::test_support::git_command(base_dir)
.args(["init", "--quiet"])
.status()
.ok()?;
status.success().then_some(())
}
fn git_add(base_dir: &Path, relative: &str) {
let status = crate::test_support::git_command(base_dir)
.args(["add", "--", relative])
.status()
.expect("git add");
assert!(status.success(), "git add {relative} failed");
}
#[test]
fn untracked_required_records_reports_a_record_git_does_not_track() {
let dir = tempfile::tempdir().expect("tempdir");
let base = dir.path();
if init_git_work_tree(base).is_none() {
return;
}
record_scaffold_owned_path(base, &base.join("packages/node/package.json")).expect("record");
assert_eq!(
untracked_required_records(base),
vec![OWNERSHIP_MANIFEST],
"a record alef just created and now depends on must be reported as untracked"
);
}
#[test]
fn untracked_required_records_is_silent_once_the_record_is_staged() {
let dir = tempfile::tempdir().expect("tempdir");
let base = dir.path();
if init_git_work_tree(base).is_none() {
return;
}
record_scaffold_owned_path(base, &base.join("packages/node/package.json")).expect("record");
git_add(base, OWNERSHIP_MANIFEST);
assert!(
untracked_required_records(base).is_empty(),
"a staged record is tracked; reporting it anyway trains the operator to ignore the warning"
);
}
#[test]
fn untracked_required_records_is_silent_outside_a_git_work_tree() {
let dir = tempfile::tempdir().expect("tempdir");
let base = dir.path();
record_scaffold_owned_path(base, &base.join("packages/node/package.json")).expect("record");
assert!(base.join(OWNERSHIP_MANIFEST).is_file(), "sanity: the record exists");
assert!(
untracked_required_records(base).is_empty(),
"with no repository to ask, tracked-ness is unanswerable and must not be reported as a fault"
);
}
#[test]
fn untracked_required_records_ignores_a_record_that_does_not_exist_yet() {
let dir = tempfile::tempdir().expect("tempdir");
let base = dir.path();
if init_git_work_tree(base).is_none() {
return;
}
assert!(untracked_required_records(base).is_empty());
}
#[test]
fn scaffold_owned_path_round_trips_and_is_idempotent() {
let dir = tempfile::tempdir().expect("tempdir");
let base = dir.path();
let target = base.join("packages/java/pom.xml");
assert!(!is_scaffold_owned_path(base, &target), "must start unrecorded");
record_scaffold_owned_path(base, &target).expect("record");
record_scaffold_owned_path(base, &target).expect("record again (idempotent)");
assert!(is_scaffold_owned_path(base, &target));
let manifest = std::fs::read_to_string(base.join(OWNERSHIP_MANIFEST)).expect("read manifest");
assert_eq!(
manifest.matches("packages/java/pom.xml").count(),
1,
"recording the same path twice must not duplicate it, got:\n{manifest}"
);
assert!(
!base.join(".alef").join(LEGACY_SCAFFOLD_OWNED_PATHS_MANIFEST).exists(),
"the gitignored legacy record must no longer be written, got:\n{manifest}"
);
}
#[test]
fn batch_recording_matches_per_path_recording_entry_for_entry() {
let batched = tempfile::tempdir().expect("tempdir");
let one_at_a_time = tempfile::tempdir().expect("tempdir");
let relatives = [
"docs/snippets/python/api/z.md",
"packages/node/package.json",
"docs/snippets/python/api/a.md",
"packages/java/pom.xml",
];
record_scaffold_owned_path(batched.path(), &batched.path().join("pre/existing.json")).expect("seed");
record_scaffold_owned_path(one_at_a_time.path(), &one_at_a_time.path().join("pre/existing.json")).expect("seed");
let joined: Vec<PathBuf> = relatives.iter().map(|rel| batched.path().join(rel)).collect();
let refs: Vec<&Path> = joined.iter().map(PathBuf::as_path).collect();
record_scaffold_owned_paths(batched.path(), &refs).expect("batch record");
record_scaffold_owned_paths(batched.path(), &refs).expect("batch record again (idempotent)");
for relative in relatives {
record_scaffold_owned_path(one_at_a_time.path(), &one_at_a_time.path().join(relative)).expect("record");
}
assert_eq!(
std::fs::read_to_string(batched.path().join(OWNERSHIP_MANIFEST)).expect("batched manifest"),
std::fs::read_to_string(one_at_a_time.path().join(OWNERSHIP_MANIFEST)).expect("sequential manifest"),
);
for relative in relatives {
assert!(is_scaffold_owned_path(batched.path(), &batched.path().join(relative)));
}
assert!(
is_scaffold_owned_path(batched.path(), &batched.path().join("pre/existing.json")),
"a batch must extend the record, never replace it"
);
}
#[test]
fn ownership_record_lives_outside_the_gitignored_cache_and_is_valid_toml() {
let dir = tempfile::tempdir().expect("tempdir");
let base = dir.path();
record_scaffold_owned_path(base, &base.join("packages/typescript/package.json")).expect("record");
let manifest_path = base.join(OWNERSHIP_MANIFEST);
assert!(manifest_path.exists(), "the record must exist at the repo root");
assert!(
!manifest_path.starts_with(base.join(CACHE_DIR)),
"the record must not live under the gitignored `{CACHE_DIR}` directory"
);
let content = std::fs::read_to_string(&manifest_path).expect("read manifest");
let parsed: OwnershipManifest = toml::from_str(&content).expect("the record must be valid TOML");
assert_eq!(parsed.owned_paths, vec!["packages/typescript/package.json".to_owned()]);
}
#[test]
fn committed_record_answers_identically_on_a_cache_less_clone() {
let warm = tempfile::tempdir().expect("tempdir warm");
let clone = tempfile::tempdir().expect("tempdir clone");
let relative = std::path::Path::new("packages/typescript/package.json");
record_scaffold_owned_path(warm.path(), &warm.path().join(relative)).expect("record");
std::fs::copy(
warm.path().join(OWNERSHIP_MANIFEST),
clone.path().join(OWNERSHIP_MANIFEST),
)
.expect("check out the committed record");
assert!(
!clone.path().join(CACHE_DIR).exists(),
"the simulated clone must have no machine-local cache"
);
assert!(
is_scaffold_owned_path(clone.path(), &clone.path().join(relative)),
"a fresh clone must agree with the warm machine about what alef owns"
);
}
#[test]
fn malformed_ownership_record_refuses_rather_than_dropping_recorded_paths() {
let dir = tempfile::tempdir().expect("tempdir");
let base = dir.path();
record_scaffold_owned_path(base, &base.join("packages/java/pom.xml")).expect("seed the record");
let manifest_path = base.join(OWNERSHIP_MANIFEST);
let seeded = std::fs::read_to_string(&manifest_path).expect("read the seeded record");
let corrupted = format!("{seeded}this line is not toml\n");
std::fs::write(&manifest_path, &corrupted).expect("hand-edit the record into invalid TOML");
let newly_scaffolded = base.join("packages/node/package.json");
let error = record_scaffold_owned_paths(base, &[newly_scaffolded.as_path()])
.expect_err("recording against an unreadable record must fail rather than rewrite it");
assert_eq!(
std::fs::read_to_string(&manifest_path).expect("read the record after the refusal"),
corrupted,
"the refused run must leave the record byte-identical, keeping every recorded path"
);
assert!(
error.to_string().contains(OWNERSHIP_MANIFEST),
"the failure must name the file the operator has to repair, got: {error}"
);
}
#[test]
fn unparseable_ownership_record_claims_nothing() {
let dir = tempfile::tempdir().expect("tempdir");
let base = dir.path();
std::fs::write(base.join(OWNERSHIP_MANIFEST), "this is not = = valid toml [[[").expect("write junk");
assert!(!is_scaffold_owned_path(
base,
&base.join("packages/typescript/package.json")
));
}
#[test]
fn ownership_record_header_does_not_read_as_a_provenance_marker() {
let rendered = render_ownership_manifest(&["packages/typescript/package.json".to_owned()]);
assert!(
!crate::core::hash::content_has_alef_marker(&rendered),
"the record's own header must not look like an alef provenance marker, got:\n{rendered}"
);
}
#[test]
fn ownership_record_escapes_paths_that_need_it() {
let dir = tempfile::tempdir().expect("tempdir");
let base = dir.path();
let awkward = "packages/we\"ird\\name.json";
record_scaffold_owned_path(base, &base.join(awkward)).expect("record");
record_scaffold_owned_path(base, &base.join("packages/plain.json")).expect("record plain");
let content = std::fs::read_to_string(base.join(OWNERSHIP_MANIFEST)).expect("read manifest");
let parsed: OwnershipManifest = toml::from_str(&content).expect("manifest must stay parseable");
assert!(
parsed.owned_paths.iter().any(|path| path == awkward),
"the awkward path must round-trip unchanged, got: {:?}",
parsed.owned_paths
);
assert!(is_scaffold_owned_path(base, &base.join(awkward)));
assert!(
is_scaffold_owned_path(base, &base.join("packages/plain.json")),
"a bad escape must not take the rest of the record down with it"
);
}
#[test]
fn scaffold_owned_path_is_scoped_to_base_dir() {
let dir_a = tempfile::tempdir().expect("tempdir a");
let dir_b = tempfile::tempdir().expect("tempdir b");
let target = std::path::PathBuf::from("packages/java/pom.xml");
record_scaffold_owned_path(dir_a.path(), &dir_a.path().join(&target)).expect("record in a");
assert!(!is_scaffold_owned_path(dir_b.path(), &dir_b.path().join(&target)));
}
#[test]
fn scaffold_owned_path_matches_across_absolute_and_relative_base_dir_spellings() {
let tmp = tempfile::tempdir().expect("tempdir");
let _cwd = crate::test_support::CwdGuard::enter(tmp.path());
let absolute_base = std::env::current_dir().expect("absolute cwd");
let relative_base = Path::new(".");
let relative_target = relative_base.join("packages/java/pom.xml");
let result = (|| -> anyhow::Result<(bool, bool)> {
record_scaffold_owned_path(&absolute_base, &absolute_base.join("packages/java/pom.xml"))?;
let found_from_relative = is_scaffold_owned_path(relative_base, &relative_target);
record_scaffold_owned_path(relative_base, &relative_base.join("packages/csharp/foo.csproj"))?;
let found_from_absolute =
is_scaffold_owned_path(&absolute_base, &absolute_base.join("packages/csharp/foo.csproj"));
Ok((found_from_relative, found_from_absolute))
})();
let (found_from_relative, found_from_absolute) = result.expect("record/check round-trip");
assert!(
found_from_relative,
"a record written with an absolute base_dir must be found by a relative-base_dir lookup"
);
assert!(
found_from_absolute,
"a record written with a relative base_dir must be found by an absolute-base_dir lookup"
);
}
#[test]
fn toml_merge_provenance_record_lives_outside_the_gitignored_cache_and_is_valid_toml() {
let dir = tempfile::tempdir().expect("tempdir");
let base = dir.path();
let mut arrays = std::collections::BTreeMap::new();
arrays.insert(
"discovery.exclude".to_string(),
vec!["target/**".to_string(), "docs/assets/**".to_string()],
);
write_toml_merge_provenance(base, Path::new("poly.toml"), &arrays).expect("write provenance");
let manifest_path = base.join(TOML_MERGE_PROVENANCE_MANIFEST);
assert!(manifest_path.exists(), "the record must exist at the repo root");
assert!(
!manifest_path.starts_with(base.join(CACHE_DIR)),
"the record must not live under the gitignored `{CACHE_DIR}` directory"
);
let content = std::fs::read_to_string(&manifest_path).expect("read manifest");
let parsed: TomlMergeProvenanceFile = toml::from_str(&content).expect("the record must be valid TOML");
assert_eq!(parsed.entries.len(), 1);
assert_eq!(parsed.entries[0].relative_path, "poly.toml");
assert_eq!(parsed.entries[0].key_path, "discovery.exclude");
assert_eq!(
parsed.entries[0].values,
vec!["target/**".to_string(), "docs/assets/**".to_string()]
);
}
#[test]
fn toml_merge_provenance_answers_identically_on_a_cache_less_clone() {
let warm = tempfile::tempdir().expect("tempdir warm");
let clone = tempfile::tempdir().expect("tempdir clone");
let mut arrays = std::collections::BTreeMap::new();
arrays.insert("discovery.exclude".to_string(), vec!["docs/assets/**".to_string()]);
write_toml_merge_provenance(warm.path(), Path::new("poly.toml"), &arrays).expect("write provenance");
std::fs::copy(
warm.path().join(TOML_MERGE_PROVENANCE_MANIFEST),
clone.path().join(TOML_MERGE_PROVENANCE_MANIFEST),
)
.expect("check out the committed record");
assert!(
!clone.path().join(CACHE_DIR).exists(),
"the simulated clone must have no machine-local cache"
);
assert_eq!(
read_toml_merge_provenance(warm.path(), Path::new("poly.toml")),
read_toml_merge_provenance(clone.path(), Path::new("poly.toml")),
"a fresh clone must agree with the warm machine about alef's prior proposal"
);
}
#[test]
fn unparseable_toml_merge_provenance_record_prunes_nothing() {
let dir = tempfile::tempdir().expect("tempdir");
let base = dir.path();
std::fs::write(
base.join(TOML_MERGE_PROVENANCE_MANIFEST),
"this is not = = valid toml [[[",
)
.expect("write junk");
assert_eq!(
read_toml_merge_provenance(base, Path::new("poly.toml")),
std::collections::BTreeMap::new()
);
}
#[test]
fn toml_merge_provenance_header_does_not_read_as_a_provenance_marker() {
assert!(
!crate::core::hash::content_has_alef_marker(TOML_MERGE_PROVENANCE_HEADER),
"the record's own header must not look like an alef provenance marker, got:\n{TOML_MERGE_PROVENANCE_HEADER}"
);
}
#[test]
fn toml_merge_provenance_write_extends_rather_than_replaces_other_targets() {
let dir = tempfile::tempdir().expect("tempdir");
let base = dir.path();
let mut poly_arrays = std::collections::BTreeMap::new();
poly_arrays.insert("discovery.exclude".to_string(), vec!["target/**".to_string()]);
write_toml_merge_provenance(base, Path::new("poly.toml"), &poly_arrays).expect("write poly.toml provenance");
let mut other_arrays = std::collections::BTreeMap::new();
other_arrays.insert("some.key".to_string(), vec!["value".to_string()]);
write_toml_merge_provenance(base, Path::new("other.toml"), &other_arrays).expect("write other.toml provenance");
assert_eq!(
read_toml_merge_provenance(base, Path::new("poly.toml")),
poly_arrays,
"recording a second merge target's provenance must leave the first's untouched"
);
assert_eq!(read_toml_merge_provenance(base, Path::new("other.toml")), other_arrays);
}
fn array_element_indents(rendered: &str) -> Vec<String> {
let mut indents = Vec::new();
let mut inside_array = false;
for line in rendered.lines() {
let trimmed = line.trim();
if inside_array {
if trimmed == "]" {
inside_array = false;
} else {
indents.push(line.chars().take_while(|character| character.is_whitespace()).collect());
}
} else if trimmed.ends_with("= [") {
inside_array = true;
}
}
indents
}
#[test]
fn both_committed_records_indent_array_elements_identically() {
let dir = tempfile::tempdir().expect("tempdir");
let base = dir.path();
let values = vec![
"packages/generated-bindings/some-language/build/**".to_string(),
"packages/generated-bindings/other-language/build/**".to_string(),
"packages/generated-bindings/third-language/build/**".to_string(),
];
let mut arrays = std::collections::BTreeMap::new();
arrays.insert("discovery.exclude".to_string(), values.clone());
write_toml_merge_provenance(base, Path::new("poly.toml"), &arrays).expect("write provenance");
let provenance = std::fs::read_to_string(base.join(TOML_MERGE_PROVENANCE_MANIFEST)).expect("read provenance");
let ownership = render_ownership_manifest(&values);
let provenance_indents = array_element_indents(&provenance);
let ownership_indents = array_element_indents(&ownership);
assert_eq!(
ownership_indents.len(),
values.len(),
"apparatus check: the ownership record must render one element line per value, got:\n{ownership}"
);
assert_eq!(
provenance_indents.len(),
values.len(),
"apparatus check: the provenance record must render one element line per value, got:\n{provenance}"
);
assert_eq!(
provenance_indents, ownership_indents,
"the two committed records must indent array elements identically, got \
{provenance_indents:?} for the provenance record and {ownership_indents:?} for the \
ownership record"
);
}
#[test]
fn record_arrays_collapse_exactly_where_the_format_gate_collapses_them() {
let short = render_record_assignment("values", &["one".to_string(), "two".to_string()]);
assert_eq!(
short, r#"values = ["one", "two"]"#,
"an array the format gate would collapse must be written inline"
);
let empty = render_record_assignment("values", &[]);
assert_eq!(empty, "values = []", "an empty array has nothing to spread over lines");
let filler = "x".repeat(RECORD_ARRAY_MAX_INLINE_WIDTH - r#"values = [""]"#.len());
let at_limit = render_record_assignment("values", std::slice::from_ref(&filler));
assert_eq!(
at_limit.chars().count(),
RECORD_ARRAY_MAX_INLINE_WIDTH,
"apparatus check: the fixture must land exactly on the limit, got:\n{at_limit}"
);
assert!(
!at_limit.contains('\n'),
"a line exactly at the limit is still collapsed by the gate, so it must stay inline"
);
let over_limit = render_record_assignment("values", &[format!("{filler}y")]);
assert_eq!(
over_limit,
format!("values = [\n{RECORD_ARRAY_INDENT}\"{filler}y\",\n]"),
"one column past the limit the gate leaves the array expanded, so alef must too"
);
}
#[test]
fn toml_merge_provenance_escapes_values_that_need_it() {
let dir = tempfile::tempdir().expect("tempdir");
let base = dir.path();
let awkward = vec!["we\"ird\\value/**".to_string(), "plain/**".to_string()];
let mut arrays = std::collections::BTreeMap::new();
arrays.insert("discovery.ex\"clude".to_string(), awkward);
write_toml_merge_provenance(base, Path::new("poly.toml"), &arrays).expect("write provenance");
assert_eq!(
read_toml_merge_provenance(base, Path::new("poly.toml")),
arrays,
"an awkward key path and value must round-trip through the hand-rolled writer unchanged"
);
}