use super::*;
use std::collections::HashMap;
use crate::analysis_db::{
BOUND_HELPER_STRUCTURAL_WIDTH_LIMIT, widen_large_bound_value_ref, widen_large_bound_values,
};
use test_util::prelude::sim_assert_eq;
fn path(value: &str) -> AbstractValue {
AbstractValue::ValuesPath(value.to_string())
}
fn string(value: &str) -> AbstractValue {
AbstractValue::StringSet(BTreeSet::from([value.to_string()]))
}
fn paths(values: &[&str]) -> BTreeSet<String> {
values
.iter()
.map(std::string::ToString::to_string)
.collect()
}
fn join(values: Vec<AbstractValue>) -> AbstractValue {
AbstractValue::join_all(values).expect("join should produce a value")
}
fn dictionary_with_width(width: usize) -> AbstractValue {
AbstractValue::Dict(
(0..width)
.map(|index| (format!("key-{index}"), path(&format!("value-{index}"))))
.collect(),
)
}
#[test]
fn structural_width_counts_leaf_alternatives_without_charging_depth() {
let nested = AbstractValue::Dict(BTreeMap::from([(
"outer".to_string(),
AbstractValue::Dict(BTreeMap::from([("inner".to_string(), path("value"))])),
)]));
let alternatives = AbstractValue::Choice(BTreeSet::from([nested.clone(), path("fallback")]));
sim_assert_eq!(have: nested.structural_width(), want: 1);
sim_assert_eq!(have: alternatives.structural_width(), want: 2);
}
#[test]
fn bound_helper_config_budget_keeps_the_boundary_and_widens_its_sibling() {
let at_limit = dictionary_with_width(BOUND_HELPER_STRUCTURAL_WIDTH_LIMIT);
let over_limit = dictionary_with_width(BOUND_HELPER_STRUCTURAL_WIDTH_LIMIT + 1);
sim_assert_eq!(
have: widen_large_bound_value_ref(
&at_limit,
"opentelemetry-collector.applyBelowBudget",
&mut BTreeSet::new(),
),
want: None
);
assert!(
matches!(
widen_large_bound_value_ref(
&over_limit,
"renamed.applyAboveBudget",
&mut BTreeSet::new(),
),
Some(AbstractValue::Top)
),
"a large config must widen independently of the helper name"
);
}
#[test]
fn bound_helper_budget_widens_a_non_config_binding() {
let mut bindings = HashMap::from([
(
"payload".to_string(),
dictionary_with_width(BOUND_HELPER_STRUCTURAL_WIDTH_LIMIT + 1),
),
("small".to_string(), path("small")),
]);
let mut widened_paths = BTreeSet::new();
widen_large_bound_values(&mut bindings, "unrelated.applyPayload", &mut widened_paths);
sim_assert_eq!(
have: bindings,
want: HashMap::from([
("payload".to_string(), AbstractValue::Top),
("small".to_string(), path("small")),
])
);
sim_assert_eq!(
have: widened_paths,
want: (0..=BOUND_HELPER_STRUCTURAL_WIDTH_LIMIT)
.map(|index| format!("value-{index}"))
.collect()
);
}
#[test]
fn join_is_idempotent() {
let value = path("image.tag");
sim_assert_eq!(have: join(vec![value.clone(), value.clone()]), want: value);
}
#[test]
fn join_is_commutative() {
let left = path("image.repository");
let right = string("nginx");
sim_assert_eq!(
have: join(vec![left.clone(), right.clone()]),
want: join(vec![right, left])
);
}
#[test]
fn join_is_associative() {
let left = path("image.repository");
let middle = string("nginx");
let right = path("image.tag");
let left_grouped = join(vec![
join(vec![left.clone(), middle.clone()]),
right.clone(),
]);
let right_grouped = join(vec![left, join(vec![middle, right])]);
sim_assert_eq!(have: left_grouped, want: right_grouped);
}
#[test]
fn top_widens_join_but_keeps_alternatives() {
sim_assert_eq!(
have: join(vec![path("image.tag"), AbstractValue::Top]),
want: AbstractValue::Choice(BTreeSet::from([AbstractValue::Top, path("image.tag")]))
);
}
#[test]
fn unknown_widens_join_but_keeps_alternatives() {
sim_assert_eq!(
have: join(vec![path("image.tag"), AbstractValue::Unknown]),
want: AbstractValue::Choice(BTreeSet::from([AbstractValue::Top, path("image.tag")]))
);
}
#[test]
fn top_inside_choice_stays_one_width_marker() {
let nested = AbstractValue::Choice(BTreeSet::from([AbstractValue::Top, path("name")]));
sim_assert_eq!(
have: join(vec![path("image.tag"), nested]),
want: AbstractValue::Choice(BTreeSet::from([
AbstractValue::Top,
path("image.tag"),
path("name"),
]))
);
}
#[test]
fn top_propagates_through_descent() {
sim_assert_eq!(
have: AbstractValue::Top.apply_to_path(&["nested".to_string()]),
want: Some(AbstractValue::Top)
);
}
#[test]
fn guard_metadata_preserves_raw_identity_through_member_selection() {
let mut metadata = HelperOutputMeta {
input_identity: true,
..HelperOutputMeta::default()
};
metadata
.predicates
.insert(BTreeSet::from([Predicate::truthy_path(
"workers.celery.enableDefault",
)]));
let value = path("workers.celery.sets.*").with_output_meta(&BTreeMap::from([(
"workers.celery.sets.*".to_string(),
metadata,
)]));
let selected = value.apply_to_path(&["securityContexts".to_string(), "pod".to_string()]);
let mut expected_meta = HelperOutputMeta {
input_identity: true,
..HelperOutputMeta::default()
};
expected_meta
.predicates
.insert(BTreeSet::from([Predicate::truthy_path(
"workers.celery.enableDefault",
)]));
let expected = AbstractValue::OutputPath(
"workers.celery.sets.*.securityContexts.pod".to_string(),
expected_meta,
);
sim_assert_eq!(have: selected.as_ref(), want: Some(&expected));
sim_assert_eq!(
have: crate::value_path_context::value_has_key(&expected, "runAsUser"),
want: Some(
Predicate::from(helm_schema_core::Guard::Absent {
path: "workers.celery.sets.*.securityContexts.pod.runAsUser".to_string(),
})
.negated()
)
);
}
#[test]
fn transformed_metadata_cannot_promote_a_guarded_path_to_input_identity() {
let metadata = HelperOutputMeta {
input_identity: true,
derived_text: true,
..HelperOutputMeta::default()
};
let value = AbstractValue::OutputPath("source".to_string(), metadata.clone());
sim_assert_eq!(
have: value.apply_to_path(&["member".to_string()]),
want: Some(AbstractValue::OutputPath("source".to_string(), metadata))
);
}
#[test]
fn omit_metadata_is_consumed_by_member_selection() {
let metadata = HelperOutputMeta {
input_identity: true,
omitted_keys: BTreeMap::from([("secret".to_string(), Vec::new())]),
..HelperOutputMeta::default()
};
let value = AbstractValue::OutputPath("service".to_string(), metadata.clone());
let mut selected_metadata = metadata.clone();
selected_metadata.omitted_keys.clear();
sim_assert_eq!(
have: value.apply_to_path(&["enabled".to_string()]),
want: Some(AbstractValue::OutputPath(
"service.enabled".to_string(),
selected_metadata
))
);
sim_assert_eq!(
have: value.apply_to_path(&["secret".to_string()]),
want: Some(AbstractValue::OutputPath("service".to_string(), metadata))
);
}
#[test]
fn omit_keys_removes_known_map_entries_but_preserves_values_root() {
let value = AbstractValue::Overlay {
entries: BTreeMap::from([
("enabled".to_string(), path("probe.enabled")),
("timeoutSeconds".to_string(), path("probe.timeoutSeconds")),
]),
fallback: Box::new(path("probe")),
};
sim_assert_eq!(
have: value.omit_keys(&BTreeSet::from(["enabled".to_string()])),
want: AbstractValue::Overlay {
entries: BTreeMap::from([(
"timeoutSeconds".to_string(),
path("probe.timeoutSeconds")
)]),
fallback: Box::new(path("probe")),
}
);
}
#[test]
fn paths_descend_structured_maps() {
let value = AbstractValue::Dict(BTreeMap::from([(
"metadata".to_string(),
AbstractValue::ValuesPath("podLabels".to_string()),
)]));
sim_assert_eq!(have: value.paths(), want: paths(&["podLabels"]));
}
#[test]
fn values_root_abstains_from_fragment_path_extraction() {
let value = AbstractValue::values_root();
sim_assert_eq!(have: value.fragment_source_paths(), want: BTreeSet::new());
sim_assert_eq!(have: value.fragment_rendered_paths(), want: BTreeSet::new());
}
#[test]
fn fragment_paths_stay_shallow_while_rendered_paths_descend_structures() {
let value = AbstractValue::Dict(BTreeMap::from([(
"metadata".to_string(),
AbstractValue::ValuesPath("podLabels".to_string()),
)]));
sim_assert_eq!(have: value.fragment_source_paths(), want: BTreeSet::new());
sim_assert_eq!(
have: value.fragment_rendered_paths(),
want: BTreeSet::from(["podLabels".to_string()])
);
}
#[test]
fn fragment_range_item_does_not_iterate_map_values() {
let value = AbstractValue::Dict(BTreeMap::from([(
"name".to_string(),
AbstractValue::ValuesPath("containers.name".to_string()),
)]));
sim_assert_eq!(have: value.fragment_range_item(), want: None);
}
#[test]
fn widened_carries_paths_for_attribution_but_is_no_fragment_source() {
let value = AbstractValue::Widened(paths(&["auth.existingSecret"]));
sim_assert_eq!(have: value.paths(), want: paths(&["auth.existingSecret"]));
sim_assert_eq!(have: value.fragment_source_paths(), want: BTreeSet::new());
sim_assert_eq!(have: value.fragment_rendered_paths(), want: BTreeSet::new());
sim_assert_eq!(have: value.apply_to_path(&["data".to_string()]), want: None);
}
#[test]
fn without_widened_drops_widened_alternatives() {
let widened = AbstractValue::Widened(paths(&["name"]));
sim_assert_eq!(have: widened.clone().without_widened(), want: None);
sim_assert_eq!(
have: join(vec![path("image.tag"), widened]).without_widened(),
want: Some(path("image.tag"))
);
}