use super::*;
use crate::cli::commands::validate_versions::{checks_pass, run as validate_versions_run};
use crate::core::config::NewAlefConfig;
use crate::test_support::{CwdGuard, git_add, git_init, write_file};
use tracing_test::traced_test;
const OLD_VERSION: &str = "1.2.0";
const NEW_VERSION: &str = "1.2.1";
fn build_workspace_with_stale_nested_lock(
root: &std::path::Path,
) -> (crate::core::config::ResolvedCrateConfig, std::path::PathBuf) {
git_init(root);
write_file(
root,
"Cargo.toml",
&format!("[workspace.package]\nversion = \"{NEW_VERSION}\"\n\n[workspace]\nresolver = \"2\"\nmembers = []\n"),
);
write_file(
root,
"e2e/rust/Cargo.toml",
&format!("[workspace]\n\n[package]\nname = \"mock-server\"\nversion = \"{OLD_VERSION}\"\nedition = \"2024\"\n"),
);
write_file(root, "e2e/rust/src/lib.rs", "");
write_file(
root,
"e2e/rust/Cargo.lock",
&format!("version = 4\n\n[[package]]\nname = \"mock-server\"\nversion = \"{OLD_VERSION}\"\n"),
);
let alef_toml = format!(
concat!(
"[workspace]\n",
"languages = [\"rust\"]\n\n",
"[[crates]]\n",
"name = \"mylib\"\n",
"sources = []\n",
"version_from = \"{cargo_toml}\"\n\n",
"[crates.e2e]\n",
"languages = []\n\n",
"[crates.e2e.call]\n",
"module = \"mylib\"\n",
"function = \"parse\"\n",
),
cargo_toml = root.join("Cargo.toml").display().to_string().replace('\\', "/"),
);
let alef_toml_path = write_file(root, "alef.toml", &alef_toml);
git_add(
root,
&[
"Cargo.toml",
"alef.toml",
"e2e/rust/Cargo.toml",
"e2e/rust/Cargo.lock",
"e2e/rust/src/lib.rs",
],
);
let cfg: NewAlefConfig = toml::from_str(&alef_toml).expect("parse alef.toml");
let mut resolved = cfg.resolve().expect("resolve config");
(resolved.remove(0), alef_toml_path)
}
#[test]
fn sync_versions_relocks_a_nested_lockfile_so_validate_versions_then_passes() {
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path();
let (config, config_path) = build_workspace_with_stale_nested_lock(root);
{
let _cwd = CwdGuard::enter(root);
sync_versions(&config, &config_path, None, true, true, None).expect("sync_versions ok");
}
let manifest = std::fs::read_to_string(root.join("e2e/rust/Cargo.toml")).expect("read e2e/rust/Cargo.toml");
assert!(
manifest.contains(&format!("version = \"{NEW_VERSION}\"")),
"e2e/rust/Cargo.toml must be bumped to {NEW_VERSION}, got:\n{manifest}"
);
let lock = std::fs::read_to_string(root.join("e2e/rust/Cargo.lock")).expect("read e2e/rust/Cargo.lock");
assert!(
lock.contains(&format!("version = \"{NEW_VERSION}\"")),
"sync_versions must relock e2e/rust/Cargo.lock to {NEW_VERSION}, not just bump the manifest \
next to a stale lock, got:\n{lock}"
);
assert!(
!lock.contains(&format!("version = \"{OLD_VERSION}\"")),
"the stale {OLD_VERSION} entry must be gone from the relocked Cargo.lock, got:\n{lock}"
);
let checks = validate_versions_run(&config, root, false).expect("validate versions must examine the fixture");
assert!(
checks_pass(&checks),
"alef validate versions must pass immediately after sync_versions -- the write set and the \
validate set must agree on the same lockfiles: {checks:?}"
);
}
#[test]
fn sync_versions_does_not_touch_a_lockfile_blocked_on_the_pending_release() {
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path();
git_init(root);
write_file(
root,
"Cargo.toml",
&format!(
"[workspace.package]\nversion = \"{NEW_VERSION}\"\n\n[workspace]\nresolver = \"2\"\nmembers = \
[\"crates/sample\"]\n"
),
);
write_file(
root,
"crates/sample/Cargo.toml",
"[package]\nname = \"sample\"\nversion.workspace = true\n",
);
write_file(root, "crates/sample/src/lib.rs", "");
write_file(
root,
"test_apps/rust/Cargo.toml",
&format!(
"[workspace]\n\n[package]\nname = \"sample-e2e-rust\"\nversion = \"{NEW_VERSION}\"\n\n\
[dependencies]\nsample_alias = {{ package = \"sample\", version = \"{NEW_VERSION}\" }}\n"
),
);
write_file(root, "test_apps/rust/src/main.rs", "fn main() {}\n");
let stale_lock_version = "1.0.9";
write_file(
root,
"test_apps/rust/Cargo.lock",
&format!(
"version = 4\n\n[[package]]\nname = \"sample\"\nversion = \"{OLD_VERSION}\"\n\
source = \"registry+https://example.invalid/index\"\n\n[[package]]\n\
name = \"sample-e2e-rust\"\nversion = \"{stale_lock_version}\"\n"
),
);
let alef_toml = format!(
"[workspace]\nlanguages = [\"rust\"]\n[[crates]]\nname = \"sample\"\nsources = []\nversion_from = \"{}\"\n",
root.join("Cargo.toml").display().to_string().replace('\\', "/")
);
let alef_toml_path = write_file(root, "alef.toml", &alef_toml);
git_add(
root,
&[
"Cargo.toml",
"alef.toml",
"crates/sample/Cargo.toml",
"crates/sample/src/lib.rs",
"test_apps/rust/Cargo.toml",
"test_apps/rust/Cargo.lock",
"test_apps/rust/src/main.rs",
],
);
let cfg: NewAlefConfig = toml::from_str(&alef_toml).expect("parse alef.toml");
let mut resolved = cfg.resolve().expect("resolve config");
let config = resolved.remove(0);
{
let _cwd = CwdGuard::enter(root);
sync_versions(&config, &alef_toml_path, None, true, true, None).expect("sync_versions ok");
}
let lock = std::fs::read_to_string(root.join("test_apps/rust/Cargo.lock")).expect("read blocked lock");
assert!(
lock.contains(&format!("version = \"{stale_lock_version}\"")),
"a lockfile pinning a registry dependency at the version being released must be left \
byte-for-byte untouched -- cargo cannot resolve it offline before publish: {lock}"
);
let checks = validate_versions_run(&config, root, false).expect("validate versions must examine the fixture");
let blocked = checks
.iter()
.find(|check| check.label == "test_apps/rust/Cargo.lock#sample-e2e-rust")
.expect("the still-stale local package row must still be reported, not silently dropped");
assert!(
!blocked.matches,
"the row genuinely does not match the canonical version yet: {blocked:?}"
);
assert_eq!(
blocked.blocked_on_publish.as_deref(),
Some(format!("sample@{NEW_VERSION}")).as_deref(),
"the row must name the release its lockfile is waiting on: {blocked:?}"
);
assert!(
checks_pass(&checks),
"a check blocked on the pending release must not fail the gate: {checks:?}"
);
}
#[traced_test]
#[test]
fn sync_versions_retries_a_lock_still_blocked_even_when_nothing_else_changed() {
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path();
git_init(root);
write_file(
root,
"Cargo.toml",
&format!(
"[workspace.package]\nversion = \"{NEW_VERSION}\"\n\n[workspace]\nresolver = \"2\"\nmembers = \
[\"crates/sample\"]\n"
),
);
write_file(
root,
"crates/sample/Cargo.toml",
"[package]\nname = \"sample\"\nversion.workspace = true\n",
);
write_file(root, "crates/sample/src/lib.rs", "");
write_file(
root,
"test_apps/rust/Cargo.toml",
&format!(
"[workspace]\n\n[package]\nname = \"sample-e2e-rust\"\nversion = \"{NEW_VERSION}\"\n\n\
[dependencies]\nsample_alias = {{ package = \"sample\", version = \"{NEW_VERSION}\" }}\n"
),
);
write_file(root, "test_apps/rust/src/main.rs", "fn main() {}\n");
let stale_lock_version = "1.0.9";
write_file(
root,
"test_apps/rust/Cargo.lock",
&format!(
"version = 4\n\n[[package]]\nname = \"sample\"\nversion = \"{OLD_VERSION}\"\n\
source = \"registry+https://example.invalid/index\"\n\n[[package]]\n\
name = \"sample-e2e-rust\"\nversion = \"{stale_lock_version}\"\n"
),
);
let alef_toml = format!(
"[workspace]\nlanguages = [\"rust\"]\n[[crates]]\nname = \"sample\"\nsources = []\nversion_from = \"{}\"\n",
root.join("Cargo.toml").display().to_string().replace('\\', "/")
);
let alef_toml_path = write_file(root, "alef.toml", &alef_toml);
git_add(
root,
&[
"Cargo.toml",
"alef.toml",
"crates/sample/Cargo.toml",
"crates/sample/src/lib.rs",
"test_apps/rust/Cargo.toml",
"test_apps/rust/Cargo.lock",
"test_apps/rust/src/main.rs",
],
);
let cfg: NewAlefConfig = toml::from_str(&alef_toml).expect("parse alef.toml");
let mut resolved = cfg.resolve().expect("resolve config");
let config = resolved.remove(0);
{
let _cwd = CwdGuard::enter(root);
sync_versions(&config, &alef_toml_path, None, true, true, None).expect("sync_versions ok");
}
assert!(
logs_contain("retrying relock for a lock previously blocked on a pending release"),
"a lock still reporting blocked_on_publish must get a genuine retry attempt on every \
sync_versions call, not only the run that first bumped its manifest -- a silent skip with \
no attempt at all is exactly the shape that left test_apps/rust stale in three of four \
affected consumer repos"
);
let lock = std::fs::read_to_string(root.join("test_apps/rust/Cargo.lock")).expect("read retried lock");
assert!(
lock.contains(&format!("version = \"{stale_lock_version}\"")),
"the retry's own failure (a synthetic crate name published nowhere can never resolve) must \
still be absorbed gracefully rather than corrupting the lock: {lock}"
);
assert!(
logs_contain(&format!("still waiting on sample@{NEW_VERSION} to publish")),
"an expected, already-explained retry failure must be logged at info, not buried in a warn \
with no distinction from a genuinely unexplained failure"
);
assert!(
!logs_contain("Resolve the dependency conflict in that directory"),
"the loud warn (with its `cargo check --locked` remedy the caller cannot run yet) must not \
fire for a retry this run already knew was blocked on a pending release"
);
}