use super::*;
use crate::test_support::{git_add, git_init, write_file};
const CANONICAL: &str = "1.2.1";
const OLD_VERSION: &str = "1.2.0";
const ALEF_MARKER: &str = "# This file is auto-generated by alef — DO NOT EDIT.\n";
#[test]
fn relock_commands_enable_registry_access_only_for_the_fallback() {
assert_eq!(relock_args(RelockMode::Offline), ["update", "--offline", "-w"]);
assert_eq!(relock_args(RelockMode::Online), ["update", "-w"]);
}
#[test]
fn relock_does_not_contact_the_registry_when_the_offline_update_succeeds() {
let mut modes = Vec::new();
let outcome = attempt_relock_with(|mode| {
modes.push(mode);
Ok(CargoStatus::success())
});
assert_eq!(modes, vec![RelockMode::Offline]);
assert!(matches!(outcome, Ok(RelockMode::Offline)));
}
#[test]
fn relock_retries_with_registry_access_when_the_offline_index_is_insufficient() {
let mut modes = Vec::new();
let outcome = attempt_relock_with(|mode| {
modes.push(mode);
Ok(match mode {
RelockMode::Offline => CargoStatus::failed(Some(101)),
RelockMode::Online => CargoStatus::success(),
})
});
assert_eq!(modes, vec![RelockMode::Offline, RelockMode::Online]);
assert!(
matches!(outcome, Ok(RelockMode::Online)),
"an online retry must recover when only the offline registry cache is stale: {outcome:?}"
);
}
#[test]
fn relock_reports_both_resolver_failures_without_a_third_attempt() {
let mut modes = Vec::new();
let outcome = attempt_relock_with(|mode| {
modes.push(mode);
Ok(match mode {
RelockMode::Offline => CargoStatus::failed(Some(101)),
RelockMode::Online => CargoStatus::failed(Some(102)),
})
});
assert_eq!(modes, vec![RelockMode::Offline, RelockMode::Online]);
assert!(
matches!(
outcome,
Err(RelockFailure::BothResolvers {
offline_code: Some(101),
online_code: Some(102),
})
),
"the diagnostic must retain each resolver's exit code: {outcome:?}"
);
}
fn write_root_manifest_with_stale_dependency(root: &std::path::Path) {
write_file(
root,
"Cargo.toml",
"[package]\nname = \"sample-core\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\n\
sample-json = \"1.26\"\n",
);
}
fn write_generated_e2e_manifest(root: &std::path::Path) {
write_file(
root,
"e2e/rust/Cargo.toml",
&format!(
"{ALEF_MARKER}[workspace]\n\n[package]\nname = \"sample-core-e2e-rust\"\nversion = \"0.1.0\"\n\
edition = \"2024\"\n\n[dependencies]\nsample_core = {{ package = \"sample-core\", path = \"../..\" }}\n"
),
);
}
fn write_stale_third_party_lock(root: &std::path::Path) {
write_file(
root,
"e2e/rust/Cargo.lock",
"version = 4\n\n\
[[package]]\nname = \"sample-core-e2e-rust\"\nversion = \"0.1.0\"\n\n\
[[package]]\nname = \"sample-core\"\nversion = \"0.1.0\"\n\n\
[[package]]\nname = \"sample-json\"\nversion = \"1.25.0\"\n",
);
}
fn write_registry_mode_harness(root: &std::path::Path) {
write_file(
root,
"Cargo.toml",
&format!(
"[workspace.package]\nversion = \"{CANONICAL}\"\n\n[workspace]\nresolver = \"2\"\n\
members = [\"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!(
"{ALEF_MARKER}[workspace]\n\n[package]\nname = \"sample-e2e-rust\"\nversion = \"{CANONICAL}\"\n\n\
[dependencies]\nsample_alias = {{ package = \"sample\", version = \"{CANONICAL}\" }}\n"
),
);
write_file(root, "test_apps/rust/src/main.rs", "fn main() {}\n");
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 = \"{CANONICAL}\"\n"
),
);
}
#[test]
fn check_release_lock_freshness_fails_on_a_drift_not_explained_by_a_pending_publish() {
let temp = tempfile::tempdir().expect("tempdir");
let root = temp.path();
git_init(root);
write_root_manifest_with_stale_dependency(root);
write_generated_e2e_manifest(root);
write_stale_third_party_lock(root);
git_add(root, &["Cargo.toml", "e2e/rust/Cargo.toml", "e2e/rust/Cargo.lock"]);
let error =
check_release_lock_freshness(root, "0.1.0").expect("a lock stale for an unrelated reason must fail the gate");
let message = format!("{error:#}");
assert!(
message.contains("sample-json"),
"message must name the dependency: {message}"
);
assert!(message.contains("1.25.0"), "message must name the stale pin: {message}");
assert!(
message.contains("cargo update"),
"message must name the remedy: {message}"
);
}
#[test]
fn check_release_lock_freshness_tolerates_a_lock_blocked_on_the_pending_release() {
let temp = tempfile::tempdir().expect("tempdir");
let root = temp.path();
git_init(root);
write_registry_mode_harness(root);
git_add(
root,
&[
"Cargo.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 outcome = check_release_lock_freshness(root, CANONICAL);
assert!(
outcome.is_none(),
"a lock waiting on this release's own unpublished version must not fail the gate: {outcome:?}"
);
}
#[test]
fn check_release_lock_freshness_passes_a_resolvable_lock() {
let temp = tempfile::tempdir().expect("tempdir");
let root = temp.path();
git_init(root);
write_root_manifest_with_stale_dependency(root);
write_generated_e2e_manifest(root);
write_file(
root,
"e2e/rust/Cargo.lock",
"version = 4\n\n\
[[package]]\nname = \"sample-core-e2e-rust\"\nversion = \"0.1.0\"\n\n\
[[package]]\nname = \"sample-core\"\nversion = \"0.1.0\"\n\n\
[[package]]\nname = \"sample-json\"\nversion = \"1.26.0\"\n",
);
git_add(root, &["Cargo.toml", "e2e/rust/Cargo.toml", "e2e/rust/Cargo.lock"]);
let outcome = check_release_lock_freshness(root, "0.1.0");
assert!(
outcome.is_none(),
"a lock that resolves must be reported clean: {outcome:?}"
);
}
#[test]
fn clean_dart_lock_does_not_invoke_a_resolver() {
let temp = tempfile::tempdir().expect("tempdir");
let root = temp.path();
write_file(
root,
"packages/dart/pubspec.yaml",
"name: generated_binding\nversion: 0.1.0\nenvironment:\n sdk: '>=3.0.0 <4.0.0'\n",
);
write_file(
root,
"e2e/dart/pubspec.yaml",
"name: e2e_dart\nversion: 0.1.0\ndependencies:\n generated_binding:\n path: ../../packages/dart\n",
);
write_file(
root,
"e2e/dart/pubspec.lock",
"packages:\n generated_binding:\n dependency: direct main\n description:\n path: ../../packages/dart\n relative: true\n source: path\n version: \"0.1.0\"\n",
);
let files = [GeneratedFile {
path: PathBuf::from("e2e/dart/pubspec.yaml"),
content: std::fs::read_to_string(root.join("e2e/dart/pubspec.yaml")).expect("pubspec"),
generated_header: true,
}];
let mut calls = 0;
relock_dart_lockfiles_with(&files, root, &HashSet::new(), |_, _| {
calls += 1;
Ok(CargoStatus::success())
});
assert_eq!(
calls, 0,
"a byte-stable manifest with a satisfying lock must do no resolver work"
);
}
#[test]
fn dart_lock_detects_a_stale_exact_pin_from_a_path_dependency() {
let temp = tempfile::tempdir().expect("tempdir");
let root = temp.path();
write_file(
root,
"packages/dart/pubspec.yaml",
"name: generated_binding\nversion: 0.1.0\ndependencies:\n flutter_rust_bridge: 2.13.0\n",
);
write_file(
root,
"e2e/dart/pubspec.yaml",
"name: e2e_dart\nversion: 0.1.0\ndependencies:\n generated_binding:\n path: ../../packages/dart\n",
);
write_file(
root,
"e2e/dart/pubspec.lock",
"packages:\n generated_binding:\n version: \"0.1.0\"\n flutter_rust_bridge:\n version: \"2.12.0\"\n",
);
assert!(
dart_lock_has_stale_declared_pin(&root.join("e2e/dart")),
"the unchanged e2e manifest must notice its path dependency's changed exact FRB pin"
);
write_file(
root,
"e2e/dart/pubspec.lock",
"packages:\n generated_binding:\n version: \"0.1.0\"\n flutter_rust_bridge:\n version: \"2.13.0\"\n",
);
assert!(!dart_lock_has_stale_declared_pin(&root.join("e2e/dart")));
}
#[test]
fn dart_relock_retries_online_after_offline_resolution_failure() {
let mut modes = Vec::new();
let outcome = attempt_dart_relock_with(|mode| {
modes.push(mode);
Ok(match mode {
DartRelockMode::Offline => CargoStatus::failed(Some(69)),
DartRelockMode::Online => CargoStatus::success(),
})
});
assert_eq!(outcome.expect("online fallback succeeds"), DartRelockMode::Online);
assert_eq!(modes, vec![DartRelockMode::Offline, DartRelockMode::Online]);
}