helm-schema-k8s 0.0.4

Generate an accurate JSON schema for any helm chart
Documentation
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};

use test_util::prelude::sim_assert_eq;

use super::{cache_home_for, default_cache_dir};

/// A scripted environment for [`cache_home_for`], so both platform branches
/// run under every host OS in CI. The logic under test is variable
/// precedence, not path syntax, so values are built with [`abs`].
fn env(pairs: &[(&str, &Path)]) -> impl Fn(&str) -> Option<PathBuf> {
    let map: BTreeMap<String, PathBuf> = pairs
        .iter()
        .map(|(key, value)| ((*key).to_string(), value.to_path_buf()))
        .collect();
    move |var: &str| map.get(var).cloned()
}

/// `tail` placed directly under the host's filesystem root.
///
/// [`cache_home_for`] discards relative values and judges absoluteness with
/// the host's rules, so scripted values must be absolute *there*: a bare
/// `/xdg` has a root but no drive prefix, which makes it relative on
/// Windows. Hardcoding the Unix spelling made every Windows-host run
/// discard the whole environment and answer `None`, so the precedence the
/// tests exist to pin was never reached.
fn abs(tail: &str) -> PathBuf {
    let path = if cfg!(windows) {
        PathBuf::from(format!(r"C:\{tail}"))
    } else {
        PathBuf::from(format!("/{tail}"))
    };
    // Without this, a value that is relative on the host would fail the
    // precedence assertions as a bare `None`, which says nothing about why.
    assert!(
        path.is_absolute(),
        "scripted value must be absolute on this host, got {}",
        path.display()
    );
    path
}

/// The resolved root must never be working-directory-relative, whatever the
/// environment looks like. Windows sets neither `XDG_CACHE_HOME` nor `HOME`,
/// and an earlier fallback returned a bare `.cache` there — so the cache
/// landed wherever the process happened to be launched from, and two runs
/// over the same chart from different directories consulted different caches.
#[test]
fn default_cache_dir_is_always_absolute() {
    let root = default_cache_dir(
        "HELM_SCHEMA_TEST_CACHE_ROOT_UNSET",
        "kubernetes-json-schema",
    );
    assert!(
        root.is_absolute(),
        "cache root must be absolute, got {}",
        root.display()
    );
}

/// The leaf keeps the two managed roots apart under one cache home.
#[test]
fn default_cache_dir_separates_managed_roots_by_leaf() {
    let k8s = default_cache_dir(
        "HELM_SCHEMA_TEST_CACHE_ROOT_UNSET",
        "kubernetes-json-schema",
    );
    let crd = default_cache_dir("HELM_SCHEMA_TEST_CACHE_ROOT_UNSET", "crds-catalog");

    assert_ne!(k8s, crd, "managed roots must not share a directory");
    sim_assert_eq!(have: k8s.parent(), want: crd.parent());
    sim_assert_eq!(
        have: k8s.file_name().and_then(std::ffi::OsStr::to_str),
        want: Some("kubernetes-json-schema")
    );
}

#[test]
fn unix_prefers_xdg_cache_home_over_home() {
    let (xdg, home) = (abs("xdg"), abs("home/u"));
    sim_assert_eq!(
        have: cache_home_for(false, env(&[("XDG_CACHE_HOME", &xdg), ("HOME", &home)])),
        want: Some(xdg)
    );
}

#[test]
fn unix_home_fallback_appends_dot_cache() {
    let home = abs("home/u");
    sim_assert_eq!(
        have: cache_home_for(false, env(&[("HOME", &home)])),
        want: Some(home.join(".cache"))
    );
}

/// The XDG basedir rule: a relative path in these variables is invalid and
/// ignored. Honoring it would anchor the "per-user" cache to the working
/// directory.
#[test]
fn unix_relative_xdg_cache_home_is_ignored() {
    let home = abs("home/u");
    sim_assert_eq!(
        have: cache_home_for(
            false,
            env(&[("XDG_CACHE_HOME", Path::new("rel/cache")), ("HOME", &home)]),
        ),
        want: Some(home.join(".cache"))
    );
}

#[test]
fn unix_without_profile_yields_none() {
    sim_assert_eq!(have: cache_home_for(false, env(&[])), want: None);
}

#[test]
fn windows_prefers_localappdata() {
    let (local, profile) = (abs("win/local"), abs("win/profile"));
    sim_assert_eq!(
        have: cache_home_for(
            true,
            env(&[("LOCALAPPDATA", &local), ("USERPROFILE", &profile)]),
        ),
        want: Some(local)
    );
}

#[test]
fn windows_userprofile_fallback_appends_appdata_local() {
    let profile = abs("win/profile");
    sim_assert_eq!(
        have: cache_home_for(true, env(&[("USERPROFILE", &profile)])),
        want: Some(profile.join("AppData").join("Local"))
    );
}

/// cmd and PowerShell leave HOME unset while MSYS shells set it elsewhere;
/// consulting it would move the cache depending on the launching shell. The
/// Windows branch must resolve from Windows variables only.
#[test]
fn windows_ignores_home() {
    sim_assert_eq!(
        have: cache_home_for(true, env(&[("HOME", &abs("home/u"))])),
        want: None
    );
}

#[test]
fn windows_relative_localappdata_is_ignored() {
    let profile = abs("win/profile");
    sim_assert_eq!(
        have: cache_home_for(
            true,
            env(&[("LOCALAPPDATA", Path::new("rel/local")), ("USERPROFILE", &profile)]),
        ),
        want: Some(profile.join("AppData").join("Local"))
    );
}