use super::*;
#[test]
fn sync_versions_writes_root_and_node_crate_package_json() {
use crate::core::config::NewAlefConfig;
let _guard = CWD_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let original_cwd = std::env::current_dir().expect("cwd");
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path();
std::fs::write(
root.join("Cargo.toml"),
"[workspace.package]\nversion = \"1.0.0\"\n\n[workspace]\nresolver = \"2\"\nmembers = []\n",
)
.expect("write Cargo.toml");
std::fs::write(
root.join("package.json"),
"{\n \"name\": \"mylib-root\",\n \"version\": \"0.9.0\",\n \"private\": true\n}\n",
)
.expect("write root package.json");
std::fs::create_dir_all(root.join("crates/mylib-node")).expect("mkdir crates/mylib-node");
std::fs::write(
root.join("crates/mylib-node/package.json"),
"{\n \"name\": \"mylib\",\n \"version\": \"0.9.0\"\n}\n",
)
.expect("write crates/mylib-node/package.json");
let alef_toml = format!(
"[workspace]\nlanguages = [\"node\"]\n[[crates]]\nname = \"mylib\"\nsources = []\nversion_from = \"{}\"\n",
root.join("Cargo.toml").display().to_string().replace('\\', "/")
);
let alef_toml_path = root.join("alef.toml");
std::fs::write(&alef_toml_path, &alef_toml).expect("write alef.toml");
let cfg: NewAlefConfig = toml::from_str(&alef_toml).expect("parse alef.toml");
let mut resolved = cfg.resolve().expect("resolve config");
let resolved_cfg = resolved.remove(0);
std::env::set_current_dir(root).expect("set_current_dir");
let sync_result = sync_versions(&resolved_cfg, &alef_toml_path, None, true, true, None);
let _ = std::env::set_current_dir(&original_cwd);
sync_result.expect("sync_versions ok");
let root_pkg = std::fs::read_to_string(root.join("package.json")).expect("read root package.json");
assert!(
root_pkg.contains(r#""version": "1.0.0""#),
"root package.json must be bumped to canonical version, got:\n{root_pkg}"
);
assert!(
!root_pkg.contains("0.9.0"),
"old version must be gone from root package.json, got:\n{root_pkg}"
);
let node_pkg = std::fs::read_to_string(root.join("crates/mylib-node/package.json"))
.expect("read crates/mylib-node/package.json");
assert!(
node_pkg.contains(r#""version": "1.0.0""#),
"crates/*-node/package.json must be bumped to canonical version, got:\n{node_pkg}"
);
}
#[test]
fn sync_versions_bumps_napi_platform_pins_and_manifests() {
use crate::core::config::NewAlefConfig;
let _guard = CWD_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let original_cwd = std::env::current_dir().expect("cwd");
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path();
std::fs::write(
root.join("Cargo.toml"),
"[workspace.package]\nversion = \"1.0.0\"\n\n[workspace]\nresolver = \"2\"\nmembers = []\n",
)
.expect("write Cargo.toml");
std::fs::create_dir_all(root.join("crates/mylib-node")).expect("mkdir crates/mylib-node");
std::fs::write(
root.join("crates/mylib-node/package.json"),
"{\n \"name\": \"@scope/mylib\",\n \"version\": \"0.9.0\",\n \"optionalDependencies\": {\n \"@scope/mylib-linux-x64-gnu\": \"0.9.0\",\n \"@scope/mylib-darwin-arm64\": \"0.9.0\",\n \"@scope/mylib-win32-x64-msvc\": \"0.9.0\"\n }\n}\n",
)
.expect("write crates/mylib-node/package.json");
for platform in &["linux-x64-gnu", "darwin-arm64", "win32-x64-msvc"] {
let dir = root.join(format!("crates/mylib-node/npm/{platform}"));
std::fs::create_dir_all(&dir).expect("mkdir platform dir");
std::fs::write(
dir.join("package.json"),
format!("{{\n \"name\": \"@scope/mylib-{platform}\",\n \"version\": \"0.9.0\"\n}}\n"),
)
.expect("write platform package.json");
}
let alef_toml = format!(
"[workspace]\nlanguages = [\"node\"]\n[[crates]]\nname = \"mylib\"\nsources = []\nversion_from = \"{}\"\n",
root.join("Cargo.toml").display().to_string().replace('\\', "/")
);
let alef_toml_path = root.join("alef.toml");
std::fs::write(&alef_toml_path, &alef_toml).expect("write alef.toml");
let cfg: NewAlefConfig = toml::from_str(&alef_toml).expect("parse alef.toml");
let mut resolved = cfg.resolve().expect("resolve config");
let resolved_cfg = resolved.remove(0);
std::env::set_current_dir(root).expect("set_current_dir");
let sync_result = sync_versions(&resolved_cfg, &alef_toml_path, None, true, true, None);
let _ = std::env::set_current_dir(&original_cwd);
sync_result.expect("sync_versions ok");
let crate_pkg = std::fs::read_to_string(root.join("crates/mylib-node/package.json"))
.expect("read crates/mylib-node/package.json");
assert!(
!crate_pkg.contains("0.9.0"),
"old version must be gone from crates/mylib-node/package.json (including optionalDependencies), got:\n{crate_pkg}"
);
assert!(
crate_pkg.contains(r#""@scope/mylib-linux-x64-gnu": "1.0.0""#),
"optionalDependencies pin to linux-x64-gnu must be bumped, got:\n{crate_pkg}"
);
assert!(
crate_pkg.contains(r#""@scope/mylib-darwin-arm64": "1.0.0""#),
"optionalDependencies pin to darwin-arm64 must be bumped, got:\n{crate_pkg}"
);
assert!(
crate_pkg.contains(r#""@scope/mylib-win32-x64-msvc": "1.0.0""#),
"optionalDependencies pin to win32-x64-msvc must be bumped, got:\n{crate_pkg}"
);
for platform in &["linux-x64-gnu", "darwin-arm64", "win32-x64-msvc"] {
let manifest = std::fs::read_to_string(root.join(format!("crates/mylib-node/npm/{platform}/package.json")))
.expect("read platform package.json");
assert!(
manifest.contains(r#""version": "1.0.0""#),
"platform manifest {platform} must be bumped, got:\n{manifest}"
);
assert!(
!manifest.contains("0.9.0"),
"old version must be gone from platform manifest {platform}, got:\n{manifest}"
);
}
}
#[test]
fn sync_versions_bumps_both_python_pyprojects_to_pep440_prerelease() {
use crate::core::config::NewAlefConfig;
let _guard = CWD_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let original_cwd = std::env::current_dir().expect("cwd");
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path();
std::fs::write(
root.join("Cargo.toml"),
"[workspace.package]\nversion = \"0.15.6-rc.2\"\n\n[workspace]\nresolver = \"2\"\nmembers = []\n",
)
.expect("write Cargo.toml");
std::fs::create_dir_all(root.join("packages/python")).expect("mkdir packages/python");
std::fs::write(
root.join("packages/python/pyproject.toml"),
"[project]\nname = \"mylib\"\nversion = \"0.15.5\"\n",
)
.expect("write packages/python/pyproject.toml");
std::fs::create_dir_all(root.join("crates/mylib-py/src")).expect("mkdir crates/mylib-py/src");
std::fs::write(
root.join("crates/mylib-py/src/pyproject.toml"),
"[project]\nname = \"mylib\"\nversion = \"0.15.5\"\n",
)
.expect("write crates/mylib-py/src/pyproject.toml");
let alef_toml = format!(
"[workspace]\nlanguages = [\"python\"]\n[[crates]]\nname = \"mylib\"\nsources = []\nversion_from = \"{}\"\n[crates.output]\npython = \"crates/mylib-py/src/\"\n",
root.join("Cargo.toml").display().to_string().replace('\\', "/")
);
let alef_toml_path = root.join("alef.toml");
std::fs::write(&alef_toml_path, &alef_toml).expect("write alef.toml");
let cfg: NewAlefConfig = toml::from_str(&alef_toml).expect("parse alef.toml");
let mut resolved = cfg.resolve().expect("resolve config");
let resolved_cfg = resolved.remove(0);
std::env::set_current_dir(root).expect("set_current_dir");
let sync_result = sync_versions(&resolved_cfg, &alef_toml_path, None, true, true, None);
let _ = std::env::set_current_dir(&original_cwd);
sync_result.expect("sync_versions ok");
let consumer =
std::fs::read_to_string(root.join("packages/python/pyproject.toml")).expect("read consumer pyproject");
assert!(
consumer.contains(r#"version = "0.15.6rc2""#),
"consumer pyproject must be PEP 440 normalised, got:\n{consumer}"
);
let source = std::fs::read_to_string(root.join("crates/mylib-py/src/pyproject.toml"))
.expect("read source-template pyproject");
assert!(
source.contains(r#"version = "0.15.6rc2""#),
"source-template pyproject must be PEP 440 normalised, got:\n{source}"
);
assert!(
!source.contains("0.15.5") && !source.contains("0.15.6-rc.2"),
"source-template must hold only the normalised version, got:\n{source}"
);
}
#[test]
fn patch_workspace_dep_versions_all_dep_table_shapes() {
use std::collections::HashSet;
let dir = tempfile::tempdir().expect("tempdir");
let cargo_toml = r#"[package]
name = "crate-a"
version = "5.0.0-rc.1"
[dependencies]
crate-b = { path = "../crate-b", version = "5.0.0-rc.1", optional = true }
serde = "1.0"
[dev-dependencies]
crate-c = { path = "../crate-c", version = "5.0.0-rc.1" }
tempfile = "3"
[build-dependencies]
crate-b = { path = "../crate-b", version = "5.0.0-rc.1" }
[target.'cfg(unix)'.dependencies]
crate-b = { path = "../crate-b", version = "5.0.0-rc.1", optional = true }
libc = "0.2"
[workspace.dependencies]
crate-c = { path = "../crate-c", version = "5.0.0-rc.1", default-features = false }
tokio = { version = "1.0", features = ["full"] }
"#;
let path = dir.path().join("Cargo.toml");
std::fs::write(&path, cargo_toml).expect("write");
let members: HashSet<String> = ["crate-b", "crate-c"].iter().map(|s| s.to_string()).collect();
let changed = patch_workspace_dep_versions(path.to_str().unwrap(), "5.0.0-rc.2", &members).expect("patch ok");
assert!(changed, "at least one version pin must have been updated");
let result = std::fs::read_to_string(&path).expect("read");
let crate_b_lines: Vec<&str> = result
.lines()
.filter(|l| l.contains("crate-b") && l.contains("version"))
.collect();
assert!(
!crate_b_lines.is_empty(),
"expected crate-b dep lines with version=:\n{result}"
);
for line in &crate_b_lines {
assert!(
line.contains("5.0.0-rc.2"),
"crate-b pin not bumped:\n {line}\nfull:\n{result}"
);
}
let crate_c_lines: Vec<&str> = result
.lines()
.filter(|l| l.contains("crate-c") && l.contains("version"))
.collect();
assert!(
!crate_c_lines.is_empty(),
"expected crate-c dep lines with version=:\n{result}"
);
for line in &crate_c_lines {
assert!(
line.contains("5.0.0-rc.2"),
"crate-c pin not bumped:\n {line}\nfull:\n{result}"
);
}
assert!(
result.contains(r#"serde = "1.0""#),
"serde must not be touched:\n{result}"
);
assert!(
result.contains(r#"tempfile = "3""#),
"tempfile must not be touched:\n{result}"
);
assert!(
result.contains(r#"libc = "0.2""#),
"libc must not be touched:\n{result}"
);
assert!(
result.contains(r#"tokio = { version = "1.0", features = ["full"] }"#),
"tokio must not be touched:\n{result}"
);
}
#[test]
fn patch_workspace_dep_versions_is_idempotent() {
use std::collections::HashSet;
let dir = tempfile::tempdir().expect("tempdir");
let cargo_toml = "[package]\nname = \"crate-a\"\nversion = \"5.0.0-rc.2\"\n\n[dependencies]\ncrate-b = { path = \"../crate-b\", version = \"5.0.0-rc.2\" }\n";
let path = dir.path().join("Cargo.toml");
std::fs::write(&path, cargo_toml).expect("write");
let members: HashSet<String> = std::iter::once("crate-b".to_string()).collect();
let changed = patch_workspace_dep_versions(path.to_str().unwrap(), "5.0.0-rc.2", &members).expect("patch ok");
assert!(!changed, "no change expected when already at target version");
}
#[test]
fn patch_workspace_dep_versions_skips_path_only_deps() {
use std::collections::HashSet;
let dir = tempfile::tempdir().expect("tempdir");
let cargo_toml =
"[package]\nname = \"crate-a\"\nversion = \"1.0.0\"\n\n[dependencies]\ncrate-b = { path = \"../crate-b\" }\n";
let path = dir.path().join("Cargo.toml");
std::fs::write(&path, cargo_toml).expect("write");
let members: HashSet<String> = std::iter::once("crate-b".to_string()).collect();
let changed = patch_workspace_dep_versions(path.to_str().unwrap(), "2.0.0", &members).expect("patch ok");
assert!(!changed, "path-only deps without version= must not be touched");
}
#[test]
fn sync_versions_patches_dep_tables_on_version_change() {
use crate::core::config::NewAlefConfig;
let _guard = CWD_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let original_cwd = std::env::current_dir().expect("cwd");
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path();
fn write_file(dir: &std::path::Path, rel: &str, content: &str) {
let path = dir.join(rel);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).expect("mkdir");
}
std::fs::write(path, content).expect("write");
}
write_file(
root,
"Cargo.toml",
"[workspace.package]\nversion = \"5.0.0-rc.2\"\n\n[workspace]\nresolver = \"2\"\nmembers = [\"crates/alpha\", \"crates/beta\"]\n\n[workspace.dependencies]\nalpha = { path = \"crates/alpha\", version = \"5.0.0-rc.1\", default-features = false }\nserde = \"1.0\"\n",
);
write_file(
root,
"crates/alpha/Cargo.toml",
"[package]\nname = \"alpha\"\nversion = \"5.0.0-rc.1\"\n\n[dependencies]\nserde = \"1.0\"\n",
);
write_file(
root,
"crates/beta/Cargo.toml",
"[package]\nname = \"beta\"\nversion = \"5.0.0-rc.1\"\n\n[dependencies]\nalpha = { path = \"../alpha\", version = \"5.0.0-rc.1\", optional = true }\nserde = \"1.0\"\n\n[dev-dependencies]\nalpha = { path = \"../alpha\", version = \"5.0.0-rc.1\" }\ntempfile = \"3\"\n\n[build-dependencies]\nalpha = { path = \"../alpha\", version = \"5.0.0-rc.1\" }\n\n[target.'cfg(unix)'.dependencies]\nalpha = { path = \"../alpha\", version = \"5.0.0-rc.1\", features = [\"unix\"] }\nlibc = \"0.2\"\n",
);
let alef_toml_content = format!(
"[workspace]\nlanguages = [\"node\"]\n[[crates]]\nname = \"alpha\"\nsources = []\nversion_from = \"{}\"\n",
root.join("Cargo.toml").display().to_string().replace('\\', "/")
);
write_file(root, "alef.toml", &alef_toml_content);
let alef_toml_path = root.join("alef.toml");
let cfg: NewAlefConfig = toml::from_str(&alef_toml_content).expect("parse alef.toml");
let mut resolved = cfg.resolve().expect("resolve");
let resolved_cfg = resolved.remove(0);
std::env::set_current_dir(root).expect("set_current_dir");
let sync_result = sync_versions(&resolved_cfg, &alef_toml_path, None, true, true, None);
let _ = std::env::set_current_dir(&original_cwd);
sync_result.expect("sync_versions ok");
let root_cargo = std::fs::read_to_string(root.join("Cargo.toml")).expect("read root");
assert!(
root_cargo.contains(r#"alpha = { path = "crates/alpha", version = "5.0.0-rc.2""#),
"root [workspace.dependencies] alpha must be bumped to rc.2:\n{root_cargo}"
);
assert!(
root_cargo.contains(r#"serde = "1.0""#),
"root serde must be untouched:\n{root_cargo}"
);
let alpha_cargo = std::fs::read_to_string(root.join("crates/alpha/Cargo.toml")).expect("read alpha");
assert!(
alpha_cargo.contains("version = \"5.0.0-rc.2\""),
"alpha [package] must be bumped:\n{alpha_cargo}"
);
let beta_cargo = std::fs::read_to_string(root.join("crates/beta/Cargo.toml")).expect("read beta");
let alpha_version_lines: Vec<&str> = beta_cargo
.lines()
.filter(|l| l.contains("alpha") && l.contains("version"))
.collect();
assert!(
!alpha_version_lines.is_empty(),
"expected alpha dep lines with version= in beta:\n{beta_cargo}"
);
for line in &alpha_version_lines {
assert!(
line.contains("5.0.0-rc.2"),
"alpha pin not bumped to rc.2 in beta:\n {line}\nfull:\n{beta_cargo}"
);
}
assert!(
!beta_cargo.contains("5.0.0-rc.1"),
"old rc.1 must be gone from beta:\n{beta_cargo}"
);
assert!(
beta_cargo.contains(r#"serde = "1.0""#),
"serde must not be touched:\n{beta_cargo}"
);
assert!(
beta_cargo.contains(r#"tempfile = "3""#),
"tempfile must not be touched:\n{beta_cargo}"
);
assert!(
beta_cargo.contains(r#"libc = "0.2""#),
"libc must not be touched:\n{beta_cargo}"
);
}
#[test]
fn run_optional_logs_but_does_not_fail_on_missing_binary() {
crate::cli::pipeline::helpers::run_optional("nonexistent_binary_12345", &["arg1", "arg2"]);
}
#[test]
fn run_optional_succeeds_for_simple_command() {
crate::cli::pipeline::helpers::run_optional("echo", &["test"]);
}
#[test]
fn sync_versions_release_date_override_wins_over_configured_date() {
use crate::core::config::NewAlefConfig;
let _guard = CWD_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let original_cwd = std::env::current_dir().expect("cwd");
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path();
std::fs::write(
root.join("Cargo.toml"),
"[workspace.package]\nversion = \"2.0.0\"\nlicense = \"MIT\"\n\n[workspace]\nresolver = \"2\"\nmembers = []\n",
)
.expect("write Cargo.toml");
let alef_toml = format!(
"[workspace]\nlanguages = [\"node\"]\n\n[workspace.citation]\ntitle = \"Tiny\"\nabstract = \"x.\"\nrepository-code = \"https://example.com/tiny\"\ndate-released = \"2020-01-01\"\n[[workspace.citation.authors]]\nname = \"Acme, Inc.\"\n\n[[crates]]\nname = \"mylib\"\nsources = []\nversion_from = \"{}\"\n",
root.join("Cargo.toml").display().to_string().replace('\\', "/")
);
let alef_toml_path = root.join("alef.toml");
std::fs::write(&alef_toml_path, &alef_toml).expect("write alef.toml");
let cfg: NewAlefConfig = toml::from_str(&alef_toml).expect("parse alef.toml");
let mut resolved = cfg.resolve().expect("resolve config");
let resolved_cfg = resolved.remove(0);
std::env::set_current_dir(root).expect("set_current_dir");
let sync_result = sync_versions(&resolved_cfg, &alef_toml_path, None, true, true, Some("2099-12-31"));
let _ = std::env::set_current_dir(&original_cwd);
sync_result.expect("sync_versions ok");
let citation = std::fs::read_to_string(root.join("CITATION.cff")).expect("CITATION.cff written");
assert!(
citation.contains("date-released: 2099-12-31\n"),
"override date must win over configured date, got:\n{citation}"
);
assert!(
!citation.contains("date-released: 2020-01-01"),
"configured date must be suppressed by override, got:\n{citation}"
);
}
#[test]
fn sync_versions_without_release_date_override_preserves_configured_date() {
use crate::core::config::NewAlefConfig;
let _guard = CWD_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let original_cwd = std::env::current_dir().expect("cwd");
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path();
std::fs::write(
root.join("Cargo.toml"),
"[workspace.package]\nversion = \"2.0.0\"\nlicense = \"MIT\"\n\n[workspace]\nresolver = \"2\"\nmembers = []\n",
)
.expect("write Cargo.toml");
let alef_toml = format!(
"[workspace]\nlanguages = [\"node\"]\n\n[workspace.citation]\ntitle = \"Tiny\"\nabstract = \"x.\"\nrepository-code = \"https://example.com/tiny\"\ndate-released = \"2020-01-01\"\n[[workspace.citation.authors]]\nname = \"Acme, Inc.\"\n\n[[crates]]\nname = \"mylib\"\nsources = []\nversion_from = \"{}\"\n",
root.join("Cargo.toml").display().to_string().replace('\\', "/")
);
let alef_toml_path = root.join("alef.toml");
std::fs::write(&alef_toml_path, &alef_toml).expect("write alef.toml");
let cfg: NewAlefConfig = toml::from_str(&alef_toml).expect("parse alef.toml");
let mut resolved = cfg.resolve().expect("resolve config");
let resolved_cfg = resolved.remove(0);
std::env::set_current_dir(root).expect("set_current_dir");
let sync_result = sync_versions(&resolved_cfg, &alef_toml_path, None, true, true, None);
let _ = std::env::set_current_dir(&original_cwd);
sync_result.expect("sync_versions ok");
let citation = std::fs::read_to_string(root.join("CITATION.cff")).expect("CITATION.cff written");
assert!(
citation.contains("date-released: 2020-01-01\n"),
"configured date must be preserved when no override is supplied, got:\n{citation}"
);
}