helm-schema-core 0.0.3

Generate an accurate JSON schema for any helm chart
Documentation
use serde_json::json;
use test_util::prelude::sim_assert_eq;

use super::GuardDnf;
use crate::{ConditionalGuard, Guard, Predicate};

fn truthy(path: &str) -> Predicate {
    Predicate::truthy_path(path)
}

#[test]
fn complementary_conjunctions_resolve_to_their_shared_key() {
    let condition = GuardDnf::from_disjunction([
        vec![truthy("enabled"), truthy("shared")],
        vec![truthy("shared"), truthy("enabled").negated()],
    ]);

    sim_assert_eq!(
        have: condition,
        want: GuardDnf::from_conjunction([truthy("shared")])
    );
}

#[test]
fn weaker_conjunction_absorbs_its_strict_superset() {
    let condition = GuardDnf::from_disjunction([
        vec![truthy("shared")],
        vec![truthy("enabled"), truthy("shared")],
    ]);

    sim_assert_eq!(
        have: condition,
        want: GuardDnf::from_conjunction([truthy("shared")])
    );
}

#[test]
fn contradictory_conjunction_is_never_live() {
    let condition = GuardDnf::from_conjunction([truthy("enabled"), truthy("enabled").negated()]);

    sim_assert_eq!(have: condition, want: GuardDnf::never());
}

#[test]
fn negated_equality_makes_its_equality_branch_never_live() {
    let equality = Predicate::from(Guard::Eq {
        path: "mode".to_string(),
        value: crate::GuardValue::string("prod"),
    });
    let condition = GuardDnf::from_conjunction([equality.clone(), equality.negated()]);

    sim_assert_eq!(have: condition, want: GuardDnf::never());
}

#[test]
fn lowered_equality_and_inequality_are_never_live() {
    let value = crate::GuardValue::string("prod");
    let condition = GuardDnf::from_guards([
        Guard::Eq {
            path: "mode".to_string(),
            value: value.clone(),
        },
        Guard::NotEq {
            path: "mode".to_string(),
            value,
        },
    ]);

    sim_assert_eq!(have: condition, want: GuardDnf::never());
}

#[test]
fn serialized_condition_uses_guard_conjunctions() {
    let condition = GuardDnf::from_disjunction([
        vec![truthy("first")],
        vec![Predicate::from(Guard::Default {
            path: "second".to_string(),
        })],
    ]);
    let serialized = serde_json::to_value(&condition).expect("serialize guard DNF");

    sim_assert_eq!(
        have: serialized,
        want: json!([
            [{"type": "truthy", "path": "first"}],
            [{"type": "default", "path": "second"}]
        ])
    );
    sim_assert_eq!(
        have: serde_json::from_value::<GuardDnf>(serialized).expect("deserialize guard DNF"),
        want: condition
    );
}

#[test]
fn serialized_condition_retains_exact_guards_from_approximate_conjunctions() {
    let condition = GuardDnf::from_disjunction([
        vec![truthy("exact")],
        vec![
            truthy("shared"),
            Predicate::approximate("condition-1", ["version".to_string()].into_iter().collect()),
        ],
    ]);

    sim_assert_eq!(
        have: serde_json::to_value(condition).expect("serialize guard DNF"),
        want: json!([
            [{"type": "truthy", "path": "shared"}],
            [{"type": "truthy", "path": "exact"}]
        ])
    );
}

#[test]
fn serialized_condition_deduplicates_approximate_branches_with_equal_exact_guards() {
    let condition = GuardDnf::from_disjunction([
        vec![
            truthy("shared"),
            Predicate::approximate("condition-1", ["first".to_string()].into_iter().collect()),
        ],
        vec![
            truthy("shared"),
            Predicate::approximate("condition-2", ["second".to_string()].into_iter().collect()),
        ],
    ]);

    sim_assert_eq!(
        have: serde_json::to_value(condition).expect("serialize guard DNF"),
        want: json!([[{"type": "truthy", "path": "shared"}]])
    );
}

#[test]
fn equal_evidence_across_opaque_branch_complements_is_unconditional() {
    let approximate =
        Predicate::approximate("condition-1", ["version".to_string()].into_iter().collect());
    let mut condition = GuardDnf::from_conjunction([approximate.clone()]);

    condition.union_absorbing(GuardDnf::from_conjunction([approximate.negated()]));

    sim_assert_eq!(have: condition, want: GuardDnf::unconditional());
}

#[test]
fn conditional_guard_disjunction_uses_the_same_normalization() {
    let condition = GuardDnf::normalize_conditional_guard_disjunction([
        vec![
            ConditionalGuard::Truthy {
                path: "enabled".to_string(),
            },
            ConditionalGuard::Truthy {
                path: "shared".to_string(),
            },
        ],
        vec![
            ConditionalGuard::Not(Box::new(ConditionalGuard::Truthy {
                path: "enabled".to_string(),
            })),
            ConditionalGuard::Truthy {
                path: "shared".to_string(),
            },
        ],
    ]);

    sim_assert_eq!(
        have: condition,
        want: vec![vec![ConditionalGuard::Truthy {
            path: "shared".to_string(),
        }]]
    );
}