alef 0.84.2

Opinionated polyglot binding generator for Rust libraries
Documentation
//! Coverage for [`check_generated_dart_lock_freshness`] / [`stale_dart_lock_findings`], the Dart
//! sibling of the cargo/composer/ruby checks. Like the composer/ruby fixtures there is no
//! path-dependency indirection: the constraint being compared lives in the one `pubspec.yaml`
//! alef generated, so the fixtures here only need that file and a `pubspec.lock` beside it.
//!
//! Unlike the internal `stale_dart_pins`/`dart_lock_blocked_on_publish` helpers in
//! `crate::cli::pipeline::version_lockfiles` (used only to decide whether `dart pub get` needs to
//! run again), this module's reader is independent and surfaces the same class of drift as an
//! actual stage failure -- see this module's own top-of-file doc for why.

use super::*;

const PUBSPEC_DIR_RELATIVE: &str = "e2e/dart";
const DART_DEPENDENCY: &str = "demo_pkg";
const DART_STALE_REQUIREMENT: &str = "1.3.0";
const DART_FRESH_PIN: &str = "1.2.3";

fn pubspec_dir(root: &Path) -> PathBuf {
    root.join(PUBSPEC_DIR_RELATIVE)
}

/// Matches `crate::e2e::codegen::dart::project::render_pubspec`'s registry-mode shape: a bare
/// exact pin under `dependencies`.
fn write_pubspec(root: &Path, requirement: &str) -> PathBuf {
    let dir = pubspec_dir(root);
    std::fs::create_dir_all(&dir).expect("create pubspec dir");
    let manifest = dir.join("pubspec.yaml");
    std::fs::write(
        &manifest,
        format!(
            "name: e2e_dart\nversion: 0.1.0\npublish_to: none\n\nenvironment:\n  sdk: '>=3.0.0 <4.0.0'\n\n\
             dependencies:\n  {DART_DEPENDENCY}: {requirement}\n"
        ),
    )
    .expect("write pubspec.yaml");
    manifest
}

fn write_pubspec_lock(root: &Path, locked_version: &str) {
    std::fs::write(
        pubspec_dir(root).join("pubspec.lock"),
        format!(
            "packages:\n  {DART_DEPENDENCY}:\n    dependency: \"direct main\"\n    description:\n      \
             name: {DART_DEPENDENCY}\n      url: \"https://pub.dev\"\n    source: hosted\n    version: \
             \"{locked_version}\"\nsdks:\n  dart: \">=3.0.0 <4.0.0\"\n"
        ),
    )
    .expect("write pubspec.lock");
}

/// The regression: `pubspec.yaml` requires an exact version the committed `pubspec.lock` pins a
/// different one for -- exactly the shape that fails `dart pub get --enforce-lockfile`. Before
/// this module alef reported nothing and exited 0 (the internal Dart check existed only to
/// decide whether to re-run `dart pub get`, never as a finding).
#[test]
fn stale_dart_lock_findings_reports_a_requirement_no_locked_version_satisfies() {
    let temp = tempfile::tempdir().expect("tempdir");
    let root = temp.path();
    write_pubspec(root, DART_STALE_REQUIREMENT);
    write_pubspec_lock(root, DART_FRESH_PIN);

    let findings = stale_dart_lock_findings(&pubspec_dir(root));

    assert_eq!(findings.len(), 1, "expected exactly one finding, got: {findings:?}");
    let finding = &findings[0];
    assert_eq!(finding.dependency, DART_DEPENDENCY);
    assert_eq!(finding.bucket, "dependencies");
    assert_eq!(finding.requirement, DART_STALE_REQUIREMENT);
    assert_eq!(finding.locked_version, DART_FRESH_PIN);
    assert_eq!(finding.lock, pubspec_dir(root).join("pubspec.lock"));
    assert_eq!(finding.declared_in, pubspec_dir(root).join("pubspec.yaml"));
}

/// The control that stops "always fail" from satisfying this suite: a lock pinning the exact
/// version `pubspec.yaml` requires must produce nothing at all.
#[test]
fn stale_dart_lock_findings_accepts_a_lock_that_matches() {
    let temp = tempfile::tempdir().expect("tempdir");
    let root = temp.path();
    write_pubspec(root, DART_FRESH_PIN);
    write_pubspec_lock(root, DART_FRESH_PIN);

    let findings = stale_dart_lock_findings(&pubspec_dir(root));

    assert!(
        findings.is_empty(),
        "a lock matching pubspec.yaml must be reported clean: {findings:?}"
    );
}

/// The one-sided rule: a package absent from the lock's `packages` map is never reported.
#[test]
fn stale_dart_lock_findings_ignores_a_dependency_absent_from_the_lock() {
    let temp = tempfile::tempdir().expect("tempdir");
    let root = temp.path();
    write_pubspec(root, DART_STALE_REQUIREMENT);
    std::fs::write(
        pubspec_dir(root).join("pubspec.lock"),
        "packages:\n  other_pkg:\n    dependency: \"direct main\"\n    source: hosted\n    version: \"2.0.0\"\n\
             sdks:\n  dart: \">=3.0.0 <4.0.0\"\n",
    )
    .expect("write pubspec.lock");

    let findings = stale_dart_lock_findings(&pubspec_dir(root));

    assert!(
        findings.is_empty(),
        "a package missing from the lock is not evidence of staleness: {findings:?}"
    );
}

/// A `path:` dependency resolves locally; it is never a pub version pin the lock's `packages` map
/// would need to satisfy, matching the cargo check's own path/git exclusion.
#[test]
fn stale_dart_lock_findings_ignores_a_path_dependency() {
    let temp = tempfile::tempdir().expect("tempdir");
    let root = temp.path();
    let dir = pubspec_dir(root);
    std::fs::create_dir_all(&dir).expect("create pubspec dir");
    std::fs::write(
        dir.join("pubspec.yaml"),
        format!(
            "name: e2e_dart\nversion: 0.1.0\n\ndependencies:\n  {DART_DEPENDENCY}:\n    path: \
             ../../packages/dart\n"
        ),
    )
    .expect("write pubspec.yaml");
    write_pubspec_lock(root, DART_FRESH_PIN);

    let findings = stale_dart_lock_findings(&dir);

    assert!(
        findings.is_empty(),
        "a path dependency carries no pub version pin to compare: {findings:?}"
    );
}

/// pub's caret operator narrows scope for a leading-zero major exactly like Cargo's own default
/// caret: `^0.2.3` only accepts patch bumps, so a lock at `0.3.0` must still be reported.
#[test]
fn stale_dart_lock_findings_rejects_a_caret_violation_on_a_zero_major() {
    let temp = tempfile::tempdir().expect("tempdir");
    let root = temp.path();
    write_pubspec(root, "^0.2.3");
    write_pubspec_lock(root, "0.3.0");

    let findings = stale_dart_lock_findings(&pubspec_dir(root));

    assert_eq!(
        findings.len(),
        1,
        "^0.2.3 must reject 0.3.0, which crossed the zero-major narrowed boundary: {findings:?}"
    );
}

/// The over-correction guard alongside the test above: `^0.2.3` must still accept a patch bump
/// within the same narrowed range.
#[test]
fn stale_dart_lock_findings_accepts_a_caret_patch_bump_on_a_zero_major() {
    let temp = tempfile::tempdir().expect("tempdir");
    let root = temp.path();
    write_pubspec(root, "^0.2.3");
    write_pubspec_lock(root, "0.2.9");

    let findings = stale_dart_lock_findings(&pubspec_dir(root));

    assert!(
        findings.is_empty(),
        "^0.2.3 must accept 0.2.9, a patch bump within the narrowed zero-major range: {findings:?}"
    );
}

/// A space-separated compound range (pub's own AND syntax) is not one this reader confidently
/// judges and must be skipped rather than risk a false positive.
#[test]
fn stale_dart_lock_findings_ignores_an_unsupported_compound_range() {
    let temp = tempfile::tempdir().expect("tempdir");
    let root = temp.path();
    write_pubspec(root, ">=1.0.0 <2.0.0");
    write_pubspec_lock(root, "5.0.0");

    let findings = stale_dart_lock_findings(&pubspec_dir(root));

    assert!(
        findings.is_empty(),
        "a compound range this reader cannot confidently judge must not be reported: {findings:?}"
    );
}

/// Alef never authors a lockfile. A generated directory without one is a consumer choice, not a
/// defect, and must not fail the run.
#[test]
fn stale_dart_lock_findings_skips_a_directory_with_no_committed_lock() {
    let temp = tempfile::tempdir().expect("tempdir");
    let root = temp.path();
    write_pubspec(root, DART_STALE_REQUIREMENT);

    let findings = stale_dart_lock_findings(&pubspec_dir(root));

    assert!(
        findings.is_empty(),
        "a directory with no lock has nothing to check: {findings:?}"
    );
}

/// The run-level entry point: it must select `pubspec.yaml` out of the generated path set, and
/// the error it returns must name the dependency, both versions, the lock, and the remedy.
#[test]
fn check_generated_dart_lock_freshness_names_the_dependency_and_the_remedy() {
    let temp = tempfile::tempdir().expect("tempdir");
    let root = temp.path();
    let manifest = write_pubspec(root, DART_STALE_REQUIREMENT);
    write_pubspec_lock(root, DART_FRESH_PIN);
    let generated: HashSet<PathBuf> = [manifest].into_iter().collect();

    let error = check_generated_dart_lock_freshness(&generated, root).expect("a stale lock must fail the run");
    let message = format!("{error:#}");

    assert!(
        message.contains(DART_DEPENDENCY),
        "message must name the dependency: {message}"
    );
    assert!(
        message.contains(DART_STALE_REQUIREMENT),
        "message must name the pubspec.yaml requirement: {message}"
    );
    assert!(
        message.contains(DART_FRESH_PIN),
        "message must name the locked version: {message}"
    );
    assert!(message.contains("dart pub"), "message must name the remedy: {message}");
    assert!(
        message.contains(&pubspec_dir(root).join("pubspec.lock").display().to_string()),
        "message must name the lock: {message}"
    );
}

/// Control for the entry point, matching the pattern above: a lock that resolves must return
/// `None` so the run keeps its zero exit.
#[test]
fn check_generated_dart_lock_freshness_passes_a_resolvable_lock() {
    let temp = tempfile::tempdir().expect("tempdir");
    let root = temp.path();
    let manifest = write_pubspec(root, DART_FRESH_PIN);
    write_pubspec_lock(root, DART_FRESH_PIN);
    let generated: HashSet<PathBuf> = [manifest].into_iter().collect();

    assert!(
        check_generated_dart_lock_freshness(&generated, root).is_none(),
        "a resolvable lock must not fail the run"
    );
}

/// A generated path set containing no `pubspec.yaml` at all must not walk anything.
#[test]
fn check_generated_dart_lock_freshness_ignores_non_manifest_paths() {
    let temp = tempfile::tempdir().expect("tempdir");
    let root = temp.path();
    write_pubspec(root, DART_STALE_REQUIREMENT);
    write_pubspec_lock(root, DART_FRESH_PIN);
    let generated: HashSet<PathBuf> = [pubspec_dir(root).join("test/basic_test.dart")].into_iter().collect();

    assert!(check_generated_dart_lock_freshness(&generated, root).is_none());
}

/// Coverage for [`check_generated_dart_lock_freshness_tolerating_pending_publish`]'s exemption --
/// the Dart sibling of the cargo/node/uv/php/ruby `pending_publish` modules.
mod pending_publish {
    use super::*;
    use crate::core::config::ResolvedCrateConfig;
    use crate::core::config::e2e::{E2eConfig, PackageRef, RegistryConfig};

    /// A crate whose `[crates.e2e.registry.packages.dart]` explicitly names `pkg_name` at
    /// `pkg_version` -- the only shape [`registry_self_dependency`] ever vouches for.
    fn resolved_cfg_with_dart_registry_package(pkg_name: &str, pkg_version: &str) -> ResolvedCrateConfig {
        let e2e = E2eConfig {
            registry: RegistryConfig {
                packages: [(
                    "dart".to_string(),
                    PackageRef {
                        name: Some(pkg_name.to_string()),
                        version: Some(pkg_version.to_string()),
                        ..PackageRef::default()
                    },
                )]
                .into_iter()
                .collect(),
                ..RegistryConfig::default()
            },
            ..E2eConfig::default()
        };
        ResolvedCrateConfig {
            e2e: Some(e2e),
            ..ResolvedCrateConfig::default()
        }
    }

    /// Control proving the exemption does real work: without it, this exact shape must still
    /// fail.
    #[test]
    fn plain_check_still_fails_on_a_pending_publish_disagreement() {
        let temp = tempfile::tempdir().expect("tempdir");
        let root = temp.path();
        let manifest = write_pubspec(root, DART_STALE_REQUIREMENT);
        write_pubspec_lock(root, DART_FRESH_PIN);
        let generated: HashSet<PathBuf> = [manifest].into_iter().collect();

        assert!(
            check_generated_dart_lock_freshness(&generated, root).is_some(),
            "control: the plain check has no pending-publish exemption and must still fail here"
        );
    }

    #[test]
    fn tolerating_variant_warns_instead_of_failing_when_the_requirement_matches_the_configured_registry_package() {
        let temp = tempfile::tempdir().expect("tempdir");
        let root = temp.path();
        let manifest = write_pubspec(root, DART_STALE_REQUIREMENT);
        write_pubspec_lock(root, DART_FRESH_PIN);
        let generated: HashSet<PathBuf> = [manifest].into_iter().collect();
        let resolved_cfg = resolved_cfg_with_dart_registry_package(DART_DEPENDENCY, DART_STALE_REQUIREMENT);

        let result =
            check_generated_dart_lock_freshness_tolerating_pending_publish(&generated, root, Some(&resolved_cfg));
        assert!(
            result.is_none(),
            "a disagreement fully explained by this crate's own configured registry \
             self-dependency must not fail the run: {result:?}"
        );
    }

    /// Without a resolved config, nothing can be classified as pending -- must behave exactly
    /// like the plain check.
    #[test]
    fn tolerating_variant_without_resolved_cfg_behaves_like_the_plain_check() {
        let temp = tempfile::tempdir().expect("tempdir");
        let root = temp.path();
        let manifest = write_pubspec(root, DART_STALE_REQUIREMENT);
        write_pubspec_lock(root, DART_FRESH_PIN);
        let generated: HashSet<PathBuf> = [manifest].into_iter().collect();

        assert!(
            check_generated_dart_lock_freshness_tolerating_pending_publish(&generated, root, None).is_some(),
            "no resolved config means no exemption is possible; this must still fail"
        );
    }

    /// The false-negative guard: a genuinely stale THIRD-PARTY pin has nothing to do with this
    /// crate's own registry self-dependency and must still fail even when a resolved config is
    /// supplied.
    #[test]
    fn tolerating_variant_still_fails_on_a_disagreement_not_explained_by_pending_publish() {
        let temp = tempfile::tempdir().expect("tempdir");
        let root = temp.path();
        let manifest = write_pubspec(root, DART_STALE_REQUIREMENT);
        write_pubspec_lock(root, DART_FRESH_PIN);
        let generated: HashSet<PathBuf> = [manifest].into_iter().collect();
        let resolved_cfg = resolved_cfg_with_dart_registry_package("unrelated_pkg", "9.9.9");

        assert!(
            check_generated_dart_lock_freshness_tolerating_pending_publish(&generated, root, Some(&resolved_cfg))
                .is_some(),
            "a third-party lock drift unrelated to this crate's own registry self-dependency \
             must still fail the run"
        );
    }
}