helm-schema-ir 0.0.7

Generate an accurate JSON schema for any helm chart
Documentation
use crate::{
    ContractProvenance, ContractUse, Guard, GuardValue, ResourceRef, SourceSpan, ValueKind,
    YamlPath,
};
use test_util::prelude::sim_assert_eq;

use super::{canonicalize_contract_uses, expand_condition_disjuncts, normalize_contract_uses};

#[test]
fn disjunct_expansion_deduplicates_identical_rows_before_subsumption() {
    let row = ContractUse::new(
        helm_schema_core::ValuesPath::parse("feature.enabled"),
        YamlPath(vec!["spec".to_string(), "enabled".to_string()]),
        ValueKind::Scalar,
        vec![Guard::Truthy {
            path: helm_schema_core::ValuesPath::parse("feature.enabled"),
        }],
        None,
    );
    let mut uses = vec![row.clone(), row];

    expand_condition_disjuncts(&mut uses);

    sim_assert_eq!(have: uses.len(), want: 1);
}

#[test]
fn canonicalization_merges_provenance_for_semantically_identical_uses() {
    let mut uses = vec![
        ContractUse {
            source_expr: helm_schema_core::ValuesPath::parse("image.tag"),
            path: YamlPath(vec!["spec".to_string(), "tag".to_string()]),
            kind: ValueKind::Scalar,
            condition: helm_schema_core::GuardDnf::from_guards(Vec::new()),
            resource: None,
            provenance: vec![ContractProvenance::new(
                "templates/a.yaml",
                SourceSpan::new(10, 20),
                Vec::new(),
            )],
            stringified: false,
            template_supplied_member_keys: std::collections::BTreeSet::default(),
            split_segment: None,
            merge_layers: None,
            range_key: false,
            nil_omitting: false,
            omitted_members: std::collections::BTreeMap::default(),
            digest: false,
            merge_operand: false,
        },
        ContractUse {
            source_expr: helm_schema_core::ValuesPath::parse("image.tag"),
            path: YamlPath(vec!["spec".to_string(), "tag".to_string()]),
            kind: ValueKind::Scalar,
            condition: helm_schema_core::GuardDnf::from_guards(Vec::new()),
            resource: None,
            provenance: vec![ContractProvenance::new(
                "templates/b.yaml",
                SourceSpan::new(30, 40),
                vec!["helper.render".to_string()],
            )],
            stringified: false,
            template_supplied_member_keys: std::collections::BTreeSet::default(),
            split_segment: None,
            merge_layers: None,
            range_key: false,
            nil_omitting: false,
            omitted_members: std::collections::BTreeMap::default(),
            digest: false,
            merge_operand: false,
        },
    ];

    canonicalize_contract_uses(&mut uses);

    sim_assert_eq!(have: uses.len(), want: 1);
    sim_assert_eq!(have: uses[0].provenance.len(), want: 2);
}

#[test]
fn canonicalization_keeps_range_key_and_value_rows_distinct() {
    let value_row = ContractUse::new(
        helm_schema_core::ValuesPath::parse("config.*"),
        YamlPath(vec!["data".to_string()]),
        ValueKind::PartialScalar,
        vec![Guard::Range {
            path: helm_schema_core::ValuesPath::parse("config"),
        }],
        None,
    );
    let mut key_row = value_row.clone();
    key_row.range_key = true;
    let mut uses = vec![key_row, value_row];

    canonicalize_contract_uses(&mut uses);

    sim_assert_eq!(have: uses.len(), want: 2);
    sim_assert_eq!(
        have: uses.iter().map(|row| row.range_key).collect::<Vec<_>>(),
        want: vec![false, true]
    );
}

#[test]
fn canonicalization_merges_complementary_conditions_across_render_sites() {
    let mut uses = vec![
        ContractUse::with_provenances(
            helm_schema_core::ValuesPath::parse("image.tag"),
            YamlPath(vec!["spec".to_string(), "tag".to_string()]),
            ValueKind::Scalar,
            vec![Guard::Truthy {
                path: helm_schema_core::ValuesPath::parse("feature.enabled"),
            }],
            None,
            vec![ContractProvenance::new(
                "templates/a.yaml",
                SourceSpan::new(10, 20),
                Vec::new(),
            )],
        ),
        ContractUse::with_provenances(
            helm_schema_core::ValuesPath::parse("image.tag"),
            YamlPath(vec!["spec".to_string(), "tag".to_string()]),
            ValueKind::Scalar,
            vec![Guard::Not {
                path: helm_schema_core::ValuesPath::parse("feature.enabled"),
            }],
            None,
            vec![ContractProvenance::new(
                "templates/b.yaml",
                SourceSpan::new(30, 40),
                Vec::new(),
            )],
        ),
    ];

    canonicalize_contract_uses(&mut uses);

    sim_assert_eq!(have: uses.len(), want: 1);
    sim_assert_eq!(have: uses[0].condition.guard_conjunctions(), want: vec![vec![]]);
    sim_assert_eq!(have: uses[0].provenance.len(), want: 2);
}

#[test]
fn canonicalization_collapses_conditions_from_the_same_render_site() {
    let provenance = ContractProvenance::new(
        "templates/deployment.yaml",
        SourceSpan::new(10, 20),
        vec!["helper.render".to_string()],
    );
    let mut uses = vec![
        ContractUse::with_provenances(
            helm_schema_core::ValuesPath::parse("image.tag"),
            YamlPath(vec!["spec".to_string(), "tag".to_string()]),
            ValueKind::Scalar,
            vec![Guard::Truthy {
                path: helm_schema_core::ValuesPath::parse("feature.enabled"),
            }],
            None,
            vec![provenance.clone()],
        ),
        ContractUse::with_provenances(
            helm_schema_core::ValuesPath::parse("image.tag"),
            YamlPath(vec!["spec".to_string(), "tag".to_string()]),
            ValueKind::Scalar,
            vec![Guard::Not {
                path: helm_schema_core::ValuesPath::parse("feature.enabled"),
            }],
            None,
            vec![provenance],
        ),
    ];

    canonicalize_contract_uses(&mut uses);

    sim_assert_eq!(have: uses.len(), want: 1);
    sim_assert_eq!(have: uses[0].condition.guard_conjunctions(), want: vec![vec![]]);
}

#[test]
fn normalization_drops_same_site_branch_subsumed_by_self_truthy_branch() {
    let provenance = ContractProvenance::new(
        "<inline:utils>",
        SourceSpan::new(1195, 1576),
        vec!["common.utils.getValueFromKey".to_string()],
    );
    let resource = Some(ResourceRef::concrete(
        "v1".to_string(),
        "Secret".to_string(),
    ));
    let base_guards = vec![Guard::NotEq {
        path: helm_schema_core::ValuesPath::parse("auth.username"),
        value: GuardValue::string("postgres"),
    }];
    let mut self_truthy_guards = base_guards.clone();
    self_truthy_guards.insert(
        0,
        Guard::Truthy {
            path: helm_schema_core::ValuesPath::parse("auth.password"),
        },
    );
    let mut uses = vec![
        ContractUse::with_provenances(
            helm_schema_core::ValuesPath::parse("auth.password"),
            YamlPath(Vec::new()),
            ValueKind::Scalar,
            base_guards,
            resource.clone(),
            vec![provenance.clone()],
        ),
        ContractUse::with_provenances(
            helm_schema_core::ValuesPath::parse("auth.password"),
            YamlPath(Vec::new()),
            ValueKind::Scalar,
            self_truthy_guards,
            resource,
            vec![provenance],
        ),
    ];

    normalize_contract_uses(&mut uses);

    sim_assert_eq!(have: uses.len(), want: 1);
    assert!(uses[0].single_guard_conjunction().iter().any(|guard| {
        matches!(guard, Guard::Truthy { path } if path.encode() == "auth.password")
    }));
}

#[test]
fn normalization_drops_subsumed_truthy_branch_across_provenance_sites() {
    let resource = Some(ResourceRef::concrete(
        "v1".to_string(),
        "Secret".to_string(),
    ));
    let base_guards = vec![Guard::NotEq {
        path: helm_schema_core::ValuesPath::parse("auth.username"),
        value: GuardValue::string("postgres"),
    }];
    let mut self_truthy_guards = base_guards.clone();
    self_truthy_guards.push(Guard::Truthy {
        path: helm_schema_core::ValuesPath::parse("auth.password"),
    });
    let mut uses = vec![
        ContractUse::with_provenances(
            helm_schema_core::ValuesPath::parse("auth.password"),
            YamlPath(Vec::new()),
            ValueKind::Scalar,
            base_guards.clone(),
            resource.clone(),
            vec![ContractProvenance::new(
                "templates/first.yaml",
                SourceSpan::new(10, 20),
                Vec::new(),
            )],
        ),
        ContractUse::with_provenances(
            helm_schema_core::ValuesPath::parse("auth.password"),
            YamlPath(Vec::new()),
            ValueKind::Scalar,
            self_truthy_guards,
            resource,
            vec![ContractProvenance::new(
                "templates/second.yaml",
                SourceSpan::new(30, 40),
                Vec::new(),
            )],
        ),
    ];

    normalize_contract_uses(&mut uses);

    sim_assert_eq!(have: uses.len(), want: 1);
    sim_assert_eq!(
        have: uses[0].single_guard_conjunction(),
        want: base_guards
    );
    sim_assert_eq!(have: uses[0].provenance.len(), want: 2);
}