use super::*;
use crate::core::backend::GeneratedFile;
use std::path::PathBuf;
fn generated(path: &str, content: &str) -> GeneratedFile {
GeneratedFile {
path: PathBuf::from(path),
content: content.to_string(),
generated_header: true,
}
}
#[test]
fn reconciliation_replaces_only_alef_owned_generated_manifests() {
let directory = tempfile::tempdir().expect("create temp directory");
let managed_path = directory.path().join("crates/sample-ffi/Cargo.toml");
let handwritten_path = directory.path().join("crates/manual/Cargo.toml");
let source_path = directory.path().join("crates/sample-ffi/src/lib.rs");
std::fs::create_dir_all(managed_path.parent().expect("managed parent")).expect("create managed directory");
std::fs::create_dir_all(handwritten_path.parent().expect("handwritten parent"))
.expect("create handwritten directory");
std::fs::create_dir_all(source_path.parent().expect("source parent")).expect("create source directory");
std::fs::write(
&managed_path,
"# This file is auto-generated by alef — DO NOT EDIT.\n# alef:hash:old\n[dependencies]\ncustom = \"1\"\n",
)
.expect("write managed manifest");
let handwritten = "[dependencies]\ncustom = \"1\"\n";
std::fs::write(&handwritten_path, handwritten).expect("write handwritten manifest");
std::fs::write(&source_path, "pub fn existing() {}\n").expect("write generated source");
let files = vec![
generated(
"crates/sample-ffi/Cargo.toml",
"[dependencies]\nserde = { version = \"1\", features = [\"derive\"] }\n",
),
generated(
"crates/manual/Cargo.toml",
"[dependencies]\nserde = { version = \"1\", features = [\"derive\"] }\n",
),
generated("crates/sample-ffi/src/lib.rs", "pub fn replaced() {}\n"),
];
let (report, lock_freshness_error) =
reconcile_managed_scaffold_manifests(&files, directory.path(), None).expect("reconcile manifests");
assert!(
lock_freshness_error.is_none(),
"no Cargo.lock exists beside this fixture's manifest, so nothing should be checked: \
{lock_freshness_error:?}"
);
let managed = std::fs::read_to_string(&managed_path).expect("read managed manifest");
assert!(
managed.contains("serde"),
"a pristine-clone manifest with Alef's standard header must receive generated dependencies"
);
assert!(
!managed.contains("custom"),
"managed manifest must match generated content"
);
assert_eq!(
std::fs::read_to_string(&handwritten_path).expect("read handwritten manifest"),
handwritten,
"an unmarked manifest must not be claimed or modified",
);
assert_eq!(
std::fs::read_to_string(&source_path).expect("read generated source"),
"pub fn existing() {}\n",
"manifest reconciliation must not widen into source regeneration",
);
assert_eq!(report.changed_paths, std::collections::HashSet::from([managed_path]));
}
#[test]
fn reconciliation_creates_missing_generated_manifest() {
let directory = tempfile::tempdir().expect("create temp directory");
let manifest = generated("crates/sample-ffi/Cargo.toml", "[dependencies]\nserde = \"1\"\n");
reconcile_managed_scaffold_manifests(&[manifest], directory.path(), None).expect("reconcile manifest");
let content =
std::fs::read_to_string(directory.path().join("crates/sample-ffi/Cargo.toml")).expect("read created manifest");
assert!(
content.contains("auto-generated by alef"),
"new managed manifest must carry Alef's generated header"
);
assert!(
content.contains("serde"),
"new managed manifest must include generated dependencies"
);
}
mod relock_still_stale_hard_fails {
use super::*;
const NATIVE_MANIFEST: &str = "packages/elixir/native/fixture_nif/Cargo.toml";
const NATIVE_LOCK: &str = "packages/elixir/native/fixture_nif/Cargo.lock";
const NATIVE_LIB: &str = "packages/elixir/native/fixture_nif/src/lib.rs";
const NONEXISTENT_DEPENDENCY: &str = "alef-fixture-nonexistent-dependency";
fn native_manifest(requirement: &str) -> GeneratedFile {
generated(
NATIVE_MANIFEST,
&format!(
"[workspace]\n\n[package]\nname = \"fixture_nif\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n\
[lib]\ncrate-type = [\"cdylib\"]\n\n\
[dependencies]\n{NONEXISTENT_DEPENDENCY} = \"{requirement}\"\n"
),
)
}
fn seed_native_crate(base: &std::path::Path, old_requirement: &str, locked_version: &str) {
let manifest_path = base.join(NATIVE_MANIFEST);
std::fs::create_dir_all(manifest_path.parent().expect("native manifest parent")).expect("mkdir native");
let seed = native_manifest(old_requirement);
std::fs::write(
&manifest_path,
format!(
"# This file is auto-generated by alef — DO NOT EDIT.\n# alef:hash:old\n{}",
seed.content
),
)
.expect("seed prior-generation manifest");
let lib_path = base.join(NATIVE_LIB);
std::fs::create_dir_all(lib_path.parent().expect("lib parent")).expect("mkdir src");
std::fs::write(&lib_path, "// placeholder\n").expect("seed native lib.rs");
std::fs::write(
base.join(NATIVE_LOCK),
format!(
"version = 3\n\n\
[[package]]\nname = \"fixture_nif\"\nversion = \"0.1.0\"\n\n\
[[package]]\nname = \"{NONEXISTENT_DEPENDENCY}\"\nversion = \"{locked_version}\"\nsource = \
\"registry+https://github.com/rust-lang/crates.io-index\"\n"
),
)
.expect("seed stale Cargo.lock");
}
#[test]
fn reconcile_fails_when_the_relocked_lock_is_still_stale() {
let directory = tempfile::tempdir().expect("create temp directory");
let base = directory.path();
seed_native_crate(base, "1.4.2", "1.4.2");
let files = vec![native_manifest("1.5.0")];
let (report, lock_freshness_error) =
reconcile_managed_scaffold_manifests(&files, base, Some("9.9.9")).expect("write itself must succeed");
assert!(
report.changed_paths.contains(&base.join(NATIVE_MANIFEST)),
"the manifest write must still be reported as changed: {:?}",
report.changed_paths
);
let manifest = std::fs::read_to_string(base.join(NATIVE_MANIFEST)).expect("read regenerated manifest");
assert!(
manifest.contains("1.5.0"),
"the manifest must carry the new requirement even though the relock failed: {manifest}"
);
let error = lock_freshness_error
.expect("a lock that cannot resolve the new requirement, offline or online, must be reported");
let message = format!("{error:#}");
assert!(message.contains(NONEXISTENT_DEPENDENCY), "got: {message}");
assert!(message.contains("1.5.0"), "got: {message}");
assert!(message.contains("1.4.2"), "got: {message}");
let lock = std::fs::read_to_string(base.join(NATIVE_LOCK)).expect("read lock");
assert!(
lock.contains("1.4.2"),
"the lock must genuinely still be stale (not merely reported so) for this assertion to mean \
anything: {lock}"
);
}
#[test]
fn every_stale_lock_is_relocked_and_named_not_just_the_first() {
const SECOND_MANIFEST: &str = "packages/ruby/ext/fixture_rb/native/Cargo.toml";
const SECOND_LOCK: &str = "packages/ruby/ext/fixture_rb/native/Cargo.lock";
const SECOND_LIB: &str = "packages/ruby/ext/fixture_rb/native/src/lib.rs";
let directory = tempfile::tempdir().expect("create temp directory");
let base = directory.path();
seed_native_crate(base, "1.4.2", "1.4.2");
let second = |requirement: &str| {
generated(
SECOND_MANIFEST,
&format!(
"[workspace]\n\n[package]\nname = \"fixture_rb\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n\
[lib]\ncrate-type = [\"cdylib\"]\n\n\
[dependencies]\n{NONEXISTENT_DEPENDENCY} = \"{requirement}\"\n"
),
)
};
let second_path = base.join(SECOND_MANIFEST);
std::fs::create_dir_all(second_path.parent().expect("second parent")).expect("mkdir second");
std::fs::write(
&second_path,
format!(
"# This file is auto-generated by alef — DO NOT EDIT.\n# alef:hash:old\n{}",
second("2.0.0").content
),
)
.expect("seed second manifest");
let second_lib = base.join(SECOND_LIB);
std::fs::create_dir_all(second_lib.parent().expect("second lib parent")).expect("mkdir second src");
std::fs::write(&second_lib, "// placeholder\n").expect("seed second lib.rs");
std::fs::write(
base.join(SECOND_LOCK),
format!(
"version = 3\n\n\
[[package]]\nname = \"fixture_rb\"\nversion = \"0.1.0\"\n\n\
[[package]]\nname = \"{NONEXISTENT_DEPENDENCY}\"\nversion = \"2.0.0\"\nsource = \
\"registry+https://github.com/rust-lang/crates.io-index\"\n"
),
)
.expect("seed second stale lock");
let files = vec![native_manifest("1.5.0"), second("2.1.0")];
let (_, lock_freshness_error) =
reconcile_managed_scaffold_manifests(&files, base, None).expect("write itself must succeed");
let error = lock_freshness_error.expect("two stale locks must be reported, not swallowed");
let message = format!("{error:#}");
assert!(
message.contains("packages/elixir/native/fixture_nif"),
"the FIRST stale lock must be named: {message}"
);
assert!(
message.contains("packages/ruby/ext/fixture_rb/native"),
"the SECOND stale lock must be named too -- naming only the first is the bug this test \
exists for: {message}"
);
assert!(
message.contains("2 generated lockfile(s)"),
"the report must state how many are unsatisfied: {message}"
);
}
#[test]
fn reconcile_fails_even_with_no_canonical_version_supplied() {
let directory = tempfile::tempdir().expect("create temp directory");
let base = directory.path();
seed_native_crate(base, "1.4.2", "1.4.2");
let files = vec![native_manifest("1.5.0")];
let (_, lock_freshness_error) =
reconcile_managed_scaffold_manifests(&files, base, None).expect("write itself must succeed");
assert!(
lock_freshness_error.is_some(),
"an unrelated third-party drift must fail even without a canonical version on hand"
);
}
}