helm-schema-k8s 0.0.4

Generate an accurate JSON schema for any helm chart
Documentation
use std::collections::BTreeMap;

use helm_schema_core::{
    ApiPresenceQuery, CapabilityGuard, CapabilityOracle, HelperBranch, HelperBranchBody,
};
use test_util::prelude::sim_assert_eq;

use super::*;

#[derive(Default)]
struct StaticOracle {
    answers: BTreeMap<String, bool>,
}

impl StaticOracle {
    fn new() -> Self {
        Self::default()
    }

    fn with(mut self, api: &str, has: bool) -> Self {
        if let Some(query) = ApiPresenceQuery::parse_helm_literal(api) {
            self.answers.insert(query.canonical_helm_literal(), has);
        }
        self
    }
}

impl CapabilityOracle for StaticOracle {
    fn capability_has_query(&self, query: &ApiPresenceQuery) -> Option<bool> {
        self.answers.get(&query.canonical_helm_literal()).copied()
    }
}

fn resource(api_version: &str, candidates: &[&str], branches: Vec<HelperBranch>) -> ResourceRef {
    ResourceRef {
        api_version: api_version.to_string(),
        kind: "Ingress".to_string(),
        kind_candidates: Vec::new(),
        api_version_candidates: candidates
            .iter()
            .map(|candidate| (*candidate).to_string())
            .collect(),
        api_version_branches: branches,
        kind_branches: Vec::new(),
    }
}

fn branch_has(api: &str, literals: &[&str]) -> HelperBranch {
    HelperBranch {
        guard: Some(CapabilityGuard::Has {
            api: api.to_string(),
        }),
        body: literal_body(literals),
    }
}

fn branch_else(literals: &[&str]) -> HelperBranch {
    HelperBranch {
        guard: None,
        body: literal_body(literals),
    }
}

fn branch_opaque(literals: &[&str]) -> HelperBranch {
    HelperBranch {
        guard: Some(CapabilityGuard::Opaque {
            text: "semverCompare \"<1.19-0\" .Capabilities.KubeVersion.GitVersion".to_string(),
        }),
        body: literal_body(literals),
    }
}

fn literal_body(literals: &[&str]) -> HelperBranchBody {
    HelperBranchBody::Literals {
        values: literals
            .iter()
            .map(|literal| (*literal).to_string())
            .collect(),
    }
}

fn planned_api_versions(candidates: &[ResourceRef]) -> Vec<String> {
    candidates
        .iter()
        .map(|candidate| candidate.api_version.clone())
        .collect()
}

#[test]
fn explicit_candidates_are_ranked_for_resolution() {
    let resource = resource("extensions/v1beta1", &["networking.k8s.io/v1"], Vec::new());
    let candidates = resource_lookup_candidates(&resource, &StaticOracle::new());

    sim_assert_eq!(
        have: planned_api_versions(&candidates),
        want: vec!["networking.k8s.io/v1", "extensions/v1beta1"]
    );
}

#[test]
fn finite_kind_candidates_cross_with_api_version_candidates() {
    let mut resource = resource("apps/v1", &[], Vec::new());
    resource.kind = "StatefulSet".to_string();
    resource.kind_candidates = vec!["Deployment".to_string()];
    let candidates = resource_lookup_candidates(&resource, &StaticOracle::new());

    sim_assert_eq!(
        have: candidates
            .into_iter()
            .map(|candidate| (candidate.api_version, candidate.kind))
            .collect::<Vec<_>>(),
        want: vec![
            ("apps/v1".to_string(), "StatefulSet".to_string()),
            ("apps/v1".to_string(), "Deployment".to_string()),
        ]
    );
}

#[test]
fn live_branch_literals_override_flat_candidates() {
    let resource = resource(
        "networking.k8s.io/v1beta1",
        &["networking.k8s.io/v1"],
        vec![
            branch_has("networking.k8s.io/v1/Ingress", &["networking.k8s.io/v1"]),
            branch_else(&["networking.k8s.io/v1beta1"]),
        ],
    );
    let oracle = StaticOracle::new().with("networking.k8s.io/v1/Ingress", true);
    let candidates = resource_lookup_candidates(&resource, &oracle);

    sim_assert_eq!(have: planned_api_versions(&candidates), want: vec!["networking.k8s.io/v1"]);
}

#[test]
fn unresolved_branches_fall_back_to_ranked_candidates() {
    let resource = resource(
        "extensions/v1beta1",
        &["networking.k8s.io/v1"],
        vec![branch_has("networking.k8s.io/v1/Ingress", &[])],
    );
    let oracle = StaticOracle::new().with("networking.k8s.io/v1/Ingress", true);
    let candidates = resource_lookup_candidates(&resource, &oracle);

    sim_assert_eq!(
        have: planned_api_versions(&candidates),
        want: vec!["networking.k8s.io/v1", "extensions/v1beta1"]
    );
}

#[test]
fn opaque_branch_guard_falls_back_to_ranked_candidates() {
    let resource = resource(
        "extensions/v1beta1",
        &["networking.k8s.io/v1"],
        vec![
            branch_opaque(&["extensions/v1beta1"]),
            branch_else(&["networking.k8s.io/v1"]),
        ],
    );
    let candidates = resource_lookup_candidates(&resource, &StaticOracle::new());

    sim_assert_eq!(
        have: planned_api_versions(&candidates),
        want: vec!["networking.k8s.io/v1", "extensions/v1beta1"]
    );
}

#[test]
fn missing_attribution_uses_first_live_branch_literal_only() {
    let resource = resource(
        "",
        &["networking.k8s.io/v1beta1", "networking.k8s.io/v1"],
        vec![
            branch_has(
                "networking.k8s.io/v1/Ingress",
                &["networking.k8s.io/v1", "networking.k8s.io/v1beta1"],
            ),
            branch_else(&["extensions/v1beta1"]),
        ],
    );
    let oracle = StaticOracle::new().with("networking.k8s.io/v1/Ingress", true);
    let candidates = missing_schema_attribution_candidates(&resource, &oracle);

    sim_assert_eq!(
        have: planned_api_versions(&candidates),
        want: vec!["networking.k8s.io/v1"]
    );
}

#[test]
fn missing_attribution_preserves_empty_primary_candidates_in_source_order() {
    let resource = resource(
        "",
        &["extensions/v1beta1", "networking.k8s.io/v1"],
        Vec::new(),
    );
    let candidates = missing_schema_attribution_candidates(&resource, &StaticOracle::new());

    sim_assert_eq!(
        have: planned_api_versions(&candidates),
        want: vec!["extensions/v1beta1", "networking.k8s.io/v1"]
    );
}

#[test]
fn missing_attribution_preserves_unresolved_branch_candidates_in_source_order() {
    let resource = resource(
        "",
        &["extensions/v1beta1", "networking.k8s.io/v1"],
        vec![branch_has("networking.k8s.io/v1/Ingress", &[])],
    );
    let oracle = StaticOracle::new().with("networking.k8s.io/v1/Ingress", true);
    let candidates = missing_schema_attribution_candidates(&resource, &oracle);

    sim_assert_eq!(
        have: planned_api_versions(&candidates),
        want: vec!["extensions/v1beta1", "networking.k8s.io/v1"]
    );
}

#[test]
fn missing_attribution_uses_primary_when_primary_is_present() {
    let resource = resource("extensions/v1beta1", &["networking.k8s.io/v1"], Vec::new());
    let candidates = missing_schema_attribution_candidates(&resource, &StaticOracle::new());

    sim_assert_eq!(
        have: planned_api_versions(&candidates),
        want: vec!["extensions/v1beta1"]
    );
}