alef 0.79.4

Opinionated polyglot binding generator for Rust libraries
Documentation
//! Coverage for [`check_release_lock_freshness`], the release-gate counterpart to
//! `lock_freshness::check_generated_lock_freshness`.
//!
//! alef #1528: the diagnostic that module provides is correct and already caught real drift, but
//! only from inside `alef generate`/`alef all` -- and a drift whose cause lives in a hand-written
//! dependency alef never watches (the `tower-http` shape) never prompts a regen, so it never fired
//! before the affected consumer repos tagged and pushed. These tests exercise the same read-only
//! check reachable from `alef validate versions` instead, and prove it does NOT regress the one
//! case that must keep passing: a lock waiting on this release's own not-yet-published version.
//!
//! No cargo invocation anywhere: `check_release_lock_freshness` is pure file reads.

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";

/// The alef marker every generated manifest carries, so `collect_alef_headered_paths` finds it
/// exactly the way it finds a real generated `Cargo.toml`.
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:?}"
    );
}

/// Root single-package crate depending on a stale third-party requirement -- mirrors
/// `lock_freshness_tests`'s own `sample-core`/`sample-json` fixture, the founding `tower-http`
/// incident's shape: the dependency that goes stale is not the crate under test's own version at
/// all, it lives one manifest away in a hand-written dependency the generated e2e crate reaches
/// only by `path`.
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",
    );
}

/// The alef-generated e2e crate, marked exactly like real generated output, depending on the
/// crate under test by path -- the indirection `relock_lockfiles_beside_changed_manifests` cannot
/// see through, since this manifest's own bytes never change when `sample-json` drifts.
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",
    );
}

/// The `test_apps/rust` shape every affected repo shared: an alef-generated, registry-mode
/// harness depending on the crate under test at exactly the canonical (about-to-release) version,
/// with a lock still pinning the previous release.
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"
        ),
    );
}

/// The regression this module exists for: a stale lock whose cause is a hand-written dependency
/// alef never watches must fail the release gate, not merely get reported the next time someone
/// happens to run `alef generate`.
#[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}"
    );
}

/// The control that must keep passing: a lock left disagreeing only because it pins this
/// release's own not-yet-published version must NOT fail the gate -- `alef validate versions`
/// already tolerates that row, and this check must defer to the exact same exemption rather than
/// re-litigating it with a stricter, independently-derived rule.
#[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:?}"
    );
}

/// The other control: a lock that resolves cleanly must report nothing at all.
#[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]);
}