use color_eyre::eyre::{self, OptionExt as _};
use indoc::indoc;
use std::collections::BTreeSet;
use crate::contract::{ContractIr, ContractUse};
use crate::{Guard, GuardValue, ResourceRef, SymbolicIrContext, ValueKind, YamlPath};
use helm_schema_ast::DefineIndex;
use helm_schema_core::{ConditionalGuard, ContractSchemaSignals, MetadataFieldKind};
use test_util::prelude::sim_assert_eq;
fn conditional_path(value: &str) -> helm_schema_core::ValuesPath {
helm_schema_core::ValuesPath::parse(value)
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct FlattenedConditionalOverlay {
target_value_path: helm_schema_core::ValuesPath,
guards: Vec<ConditionalGuard>,
evidence: helm_schema_core::ConditionalOverlayEvidence,
preserve_base_schema: bool,
}
fn signals_for(uses: Vec<ContractUse>) -> ContractSchemaSignals {
ContractIr::from_contract_uses(uses)
.finalize()
.into_schema_signals()
}
fn signals_for_template(source: &str) -> ContractSchemaSignals {
let defines = DefineIndex::new();
SymbolicIrContext::new(&defines)
.generate_contract_ir(source)
.finalize()
.into_schema_signals()
}
fn signals_for_template_at_kubernetes_version(
source: &str,
kubernetes_version: &str,
) -> ContractSchemaSignals {
let defines = DefineIndex::new();
SymbolicIrContext::with_policy(
&defines,
crate::SymbolicPolicy {
kubernetes_version: Some(kubernetes_version.to_string()),
..crate::SymbolicPolicy::default()
},
)
.generate_contract_ir(source)
.finalize()
.into_schema_signals()
}
#[test]
fn checksum_influence_does_not_own_a_dormant_provider_base() -> eyre::Result<()> {
let mut checksum = ContractUse::new(
helm_schema_core::ValuesPath::parse("secretName"),
YamlPath(vec![
"metadata".to_string(),
"annotations".to_string(),
"checksum/tls".to_string(),
]),
ValueKind::Serialized,
Vec::new(),
None,
);
checksum.digest = true;
let sink = ContractUse::new(
helm_schema_core::ValuesPath::parse("secretName"),
YamlPath(vec!["metadata".to_string(), "name".to_string()]),
ValueKind::Scalar,
vec![Guard::Truthy {
path: helm_schema_core::ValuesPath::parse("enabled"),
}],
Some(ResourceRef::concrete(
"v1".to_string(),
"Service".to_string(),
)),
);
let signals = signals_for(vec![checksum, sink]);
let evidence = signals
.evidence_for(&conditional_path("secretName"))
.ok_or_eyre("secretName evidence")?;
let overlays = evidence
.conditional_overlays
.iter()
.map(|overlay| (overlay.guards.clone(), overlay.preserve_base_schema))
.collect::<Vec<_>>();
sim_assert_eq!(
have: (
evidence.facts.has_unconditional_render_use,
evidence.facts.used_as_serialized,
overlays,
),
want: (
false,
false,
vec![(
vec![ConditionalGuard::Truthy {
path: conditional_path("enabled"),
}],
false,
)],
),
);
Ok(())
}
fn nullable_paths_for(signals: &ContractSchemaSignals) -> BTreeSet<helm_schema_core::ValuesPath> {
signals
.schema_evidence_by_value_path()
.iter()
.filter(|(_, evidence)| evidence.facts.is_nullable)
.map(|(path, _)| path.clone())
.collect()
}
fn provider_schema_uses_for(signals: &ContractSchemaSignals) -> Vec<&crate::ProviderSchemaUse> {
signals
.schema_evidence_by_value_path()
.values()
.flat_map(|evidence| evidence.provider_schema_uses.iter())
.collect()
}
fn conditional_overlays_for(signals: &ContractSchemaSignals) -> Vec<FlattenedConditionalOverlay> {
signals
.schema_evidence_by_value_path()
.iter()
.flat_map(|(target_value_path, evidence)| {
evidence
.conditional_overlays
.iter()
.cloned()
.map(|overlay| FlattenedConditionalOverlay {
target_value_path: target_value_path.clone(),
guards: overlay.guards,
evidence: overlay.evidence,
preserve_base_schema: overlay.preserve_base_schema,
})
})
.collect()
}
#[test]
fn contract_ir_nullable_paths_include_range_only_collection() {
let signals = signals_for(vec![ContractUse::new(
helm_schema_core::ValuesPath::parse("snapshot"),
YamlPath(vec!["data".to_string(), "command".to_string()]),
ValueKind::Scalar,
vec![Guard::Range {
path: helm_schema_core::ValuesPath::parse("snapshots"),
}],
None,
)]);
let nullable_paths = nullable_paths_for(&signals);
assert!(
nullable_paths.contains(&conditional_path("snapshots")),
"range sources are null-tolerant because Helm treats nil range inputs as empty: {nullable_paths:?}",
);
assert!(
!nullable_paths.contains(&conditional_path("snapshot")),
"range item values should not inherit collection nullability: {nullable_paths:?}",
);
}
#[test]
fn contract_ir_nullable_paths_require_every_render_use_to_be_tolerant() {
let signals = signals_for(vec![
ContractUse::new(
helm_schema_core::ValuesPath::parse("serviceAccount.name"),
YamlPath(vec!["metadata".to_string(), "name".to_string()]),
ValueKind::Scalar,
vec![Guard::Default {
path: helm_schema_core::ValuesPath::parse("serviceAccount.name"),
}],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("serviceAccount.name"),
YamlPath(vec!["metadata".to_string(), "namespace".to_string()]),
ValueKind::Scalar,
Vec::new(),
None,
),
]);
let nullable_paths = nullable_paths_for(&signals);
assert!(
!nullable_paths.contains(&conditional_path("serviceAccount.name")),
"one guarded render use must not make a bare render site nullable: {nullable_paths:?}",
);
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "the complete evidence scenario is clearest as one contiguous test"
)]
fn contract_ir_path_evidence_collects_references_and_typed_guard_predicates() {
let signals = signals_for(vec![
ContractUse::new(
helm_schema_core::ValuesPath::parse("podLabels"),
YamlPath(vec!["metadata".to_string(), "labels".to_string()]),
ValueKind::Fragment,
vec![
Guard::Eq {
path: helm_schema_core::ValuesPath::parse("mode"),
value: GuardValue::string("prod"),
},
Guard::TypeIs {
path: helm_schema_core::ValuesPath::parse("extraConfig"),
schema_type: "string".to_string(),
},
Guard::Truthy {
path: helm_schema_core::ValuesPath::parse("extraEnv"),
},
],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("image.tag"),
YamlPath(vec!["spec".to_string(), "image".to_string()]),
ValueKind::PartialScalar,
Vec::new(),
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("podName"),
YamlPath(vec!["metadata".to_string(), "name".to_string()]),
ValueKind::Scalar,
Vec::new(),
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("podNamespace"),
YamlPath(vec!["metadata".to_string(), "namespace".to_string()]),
ValueKind::Scalar,
Vec::new(),
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse(""),
YamlPath(vec!["metadata".to_string(), "name".to_string()]),
ValueKind::Scalar,
vec![Guard::Eq {
path: helm_schema_core::ValuesPath::parse("ignored.guard"),
value: GuardValue::string("ignored"),
}],
None,
),
]);
let evidence = signals.schema_evidence_by_value_path();
sim_assert_eq!(
have: evidence
.iter()
.filter(|(_, evidence)| evidence.is_referenced_value_path)
.map(|(path, _)| path.clone())
.collect::<BTreeSet<_>>(),
want: BTreeSet::from([
conditional_path("extraConfig"),
conditional_path("extraEnv"),
conditional_path("image.tag"),
conditional_path("mode"),
conditional_path("podLabels"),
conditional_path("podName"),
conditional_path("podNamespace"),
]),
);
sim_assert_eq!(
have: evidence
.iter()
.filter(|(_, evidence)| evidence.facts.is_ranged_source)
.map(|(path, _)| path.clone())
.collect::<BTreeSet<_>>(),
want: BTreeSet::new(),
"a control guard does not prove the path itself was the iterable",
);
sim_assert_eq!(
have: evidence
.iter()
.filter(|(_, evidence)| evidence.facts.used_as_fragment)
.map(|(path, _)| path.clone())
.collect::<BTreeSet<_>>(),
want: BTreeSet::from([conditional_path("podLabels")]),
);
sim_assert_eq!(
have: evidence
.iter()
.filter(|(_, evidence)| evidence.facts.is_partial_scalar_value_path)
.map(|(path, _)| path.clone())
.collect::<BTreeSet<_>>(),
want: BTreeSet::from([conditional_path("image.tag")]),
);
sim_assert_eq!(
have: evidence
.get(&conditional_path("podLabels"))
.map(|evidence| &evidence.metadata_field_kinds),
want: Some(&BTreeSet::new()),
"guarded metadata typing must not bind the unconditional path",
);
assert!(
evidence
.get(&conditional_path("podLabels"))
.is_some_and(|evidence| {
evidence.conditional_overlays.iter().any(|overlay| {
overlay
.evidence
.metadata_field_kinds
.contains(&MetadataFieldKind::StringMap)
})
}),
"guarded metadata typing should stay on its conditional overlay",
);
sim_assert_eq!(
have: evidence
.get(&conditional_path("podName"))
.map(|evidence| &evidence.metadata_field_kinds),
want: Some(&BTreeSet::from([MetadataFieldKind::Name])),
);
sim_assert_eq!(
have: evidence
.get(&conditional_path("podNamespace"))
.map(|evidence| &evidence.metadata_field_kinds),
want: Some(&BTreeSet::from([MetadataFieldKind::Namespace])),
);
sim_assert_eq!(
have: evidence
.get(&conditional_path("mode"))
.map(|evidence| &evidence.guard_predicates),
want: Some(&vec![ConditionalGuard::Eq {
path: conditional_path("mode"),
value: GuardValue::string("prod"),
}]),
);
sim_assert_eq!(
have: evidence
.get(&conditional_path("extraConfig"))
.map(|evidence| &evidence.guard_predicates),
want: Some(&vec![ConditionalGuard::TypeIs {
path: conditional_path("extraConfig"),
schema_type: "string".to_string(),
}]),
);
assert!(
!evidence
.get(&conditional_path("ignored.guard"))
.is_some_and(|evidence| evidence.is_referenced_value_path),
"empty-source inspection rows should not seed schema paths",
);
assert!(
!evidence.contains_key(&conditional_path("")),
"empty-source inspection rows should not seed metadata facts",
);
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "the complete evidence scenario is clearest as one contiguous test"
)]
fn contract_ir_path_evidence_preserves_values_decidable_guard_predicate_shapes() {
let signals = signals_for(vec![
ContractUse::new(
helm_schema_core::ValuesPath::parse("feature.host"),
YamlPath(vec!["spec".to_string(), "host".to_string()]),
ValueKind::Scalar,
vec![Guard::Truthy {
path: helm_schema_core::ValuesPath::parse("feature.enabled"),
}],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("feature.host"),
YamlPath(vec!["spec".to_string(), "host".to_string()]),
ValueKind::Scalar,
vec![Guard::With {
path: helm_schema_core::ValuesPath::parse("feature.config"),
}],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("feature.host"),
YamlPath(vec!["spec".to_string(), "host".to_string()]),
ValueKind::Scalar,
vec![Guard::Not {
path: helm_schema_core::ValuesPath::parse("feature.disabled"),
}],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("feature.host"),
YamlPath(vec!["spec".to_string(), "host".to_string()]),
ValueKind::Scalar,
vec![Guard::NotEq {
path: helm_schema_core::ValuesPath::parse("feature.mode"),
value: GuardValue::string("off"),
}],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("feature.host"),
YamlPath(vec!["spec".to_string(), "host".to_string()]),
ValueKind::Scalar,
vec![Guard::Absent {
path: helm_schema_core::ValuesPath::parse("feature.name"),
}],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("feature.host"),
YamlPath(vec!["spec".to_string(), "host".to_string()]),
ValueKind::Scalar,
vec![Guard::Or {
paths: vec![
helm_schema_core::ValuesPath::parse("feature.primary"),
helm_schema_core::ValuesPath::parse("feature.secondary"),
],
}],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("feature.host"),
YamlPath(vec!["spec".to_string(), "host".to_string()]),
ValueKind::Scalar,
vec![Guard::AnyOf {
alternatives: vec![
vec![
Guard::Truthy {
path: helm_schema_core::ValuesPath::parse("feature.managed"),
},
Guard::Eq {
path: helm_schema_core::ValuesPath::parse("feature.tier"),
value: GuardValue::string("prod"),
},
],
vec![Guard::Not {
path: helm_schema_core::ValuesPath::parse("feature.skip"),
}],
],
}],
None,
),
]);
let evidence = signals.schema_evidence_by_value_path();
sim_assert_eq!(
have: evidence
.get(&conditional_path("feature.enabled"))
.map(|evidence| &evidence.guard_predicates),
want: Some(&vec![ConditionalGuard::Truthy {
path: conditional_path("feature.enabled"),
}]),
);
sim_assert_eq!(
have: evidence
.get(&conditional_path("feature.config"))
.map(|evidence| &evidence.guard_predicates),
want: Some(&vec![ConditionalGuard::With {
path: conditional_path("feature.config"),
}]),
);
sim_assert_eq!(
have: evidence
.get(&conditional_path("feature.disabled"))
.map(|evidence| &evidence.guard_predicates),
want: Some(&vec![ConditionalGuard::Not(Box::new(
ConditionalGuard::Truthy {
path: conditional_path("feature.disabled"),
},
))]),
);
sim_assert_eq!(
have: evidence
.get(&conditional_path("feature.mode"))
.map(|evidence| &evidence.guard_predicates),
want: Some(&vec![ConditionalGuard::NotEq {
path: conditional_path("feature.mode"),
value: GuardValue::string("off"),
}]),
);
sim_assert_eq!(
have: evidence
.get(&conditional_path("feature.name"))
.map(|evidence| &evidence.guard_predicates),
want: Some(&vec![ConditionalGuard::Absent {
path: conditional_path("feature.name"),
}]),
);
let disjunction = ConditionalGuard::AnyOf(vec![
ConditionalGuard::Truthy {
path: conditional_path("feature.primary"),
},
ConditionalGuard::Truthy {
path: conditional_path("feature.secondary"),
},
]);
sim_assert_eq!(
have: evidence
.get(&conditional_path("feature.primary"))
.map(|evidence| &evidence.guard_predicates),
want: Some(&vec![disjunction.clone()]),
);
sim_assert_eq!(
have: evidence
.get(&conditional_path("feature.secondary"))
.map(|evidence| &evidence.guard_predicates),
want: Some(&vec![disjunction]),
);
let nested_disjunction = ConditionalGuard::AnyOf(vec![
ConditionalGuard::Not(Box::new(ConditionalGuard::Truthy {
path: conditional_path("feature.skip"),
})),
ConditionalGuard::AllOf(vec![
ConditionalGuard::Truthy {
path: conditional_path("feature.managed"),
},
ConditionalGuard::Eq {
path: conditional_path("feature.tier"),
value: GuardValue::string("prod"),
},
]),
]);
for path in ["feature.managed", "feature.tier", "feature.skip"] {
sim_assert_eq!(
have: evidence
.get(&conditional_path(path))
.map(|evidence| &evidence.guard_predicates),
want: Some(&vec![nested_disjunction.clone()]),
"expected the full nested predicate to be preserved for {path}",
);
}
}
#[test]
fn contract_ir_provider_schema_uses_are_rendered_resource_claims_only() {
let resource = ResourceRef::concrete("apps/v1".to_string(), "Deployment".to_string());
let signals = signals_for(vec![
ContractUse::new(
helm_schema_core::ValuesPath::parse("containers"),
YamlPath(vec![
"spec".to_string(),
"template".to_string(),
"spec".to_string(),
"containers".to_string(),
]),
ValueKind::Fragment,
Vec::new(),
Some(resource.clone()),
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("ports"),
YamlPath(vec!["spec".to_string(), "ports".to_string()]),
ValueKind::Scalar,
vec![Guard::Range {
path: helm_schema_core::ValuesPath::parse("ports"),
}],
Some(resource.clone()),
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("image.tag"),
YamlPath(vec!["spec".to_string(), "image".to_string()]),
ValueKind::PartialScalar,
Vec::new(),
Some(resource.clone()),
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("pathless"),
YamlPath(Vec::new()),
ValueKind::Scalar,
Vec::new(),
Some(resource.clone()),
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("noResource"),
YamlPath(vec!["metadata".to_string(), "name".to_string()]),
ValueKind::Scalar,
Vec::new(),
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse(""),
YamlPath(vec!["metadata".to_string(), "name".to_string()]),
ValueKind::Scalar,
Vec::new(),
Some(resource),
),
]);
let requests = provider_schema_uses_for(&signals);
sim_assert_eq!(have: requests.len(), want: 2, "{requests:#?}");
sim_assert_eq!(have: requests[0].value_path, want: conditional_path("containers"));
sim_assert_eq!(have: requests[0].kind, want: ValueKind::Fragment);
assert!(!requests[0].is_self_range_collection);
sim_assert_eq!(have: requests[1].value_path, want: conditional_path("ports"));
sim_assert_eq!(have: requests[1].kind, want: ValueKind::Scalar);
assert!(requests[1].is_self_range_collection);
}
#[test]
fn contract_ir_schema_signals_bundle_core_generation_facts() {
let resource = ResourceRef::concrete("apps/v1".to_string(), "Deployment".to_string());
let signals = signals_for(vec![
ContractUse::new(
helm_schema_core::ValuesPath::parse("podLabels"),
YamlPath(vec!["metadata".to_string(), "labels".to_string()]),
ValueKind::Fragment,
Vec::new(),
Some(resource.clone()),
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("serviceAccount.name"),
YamlPath(vec!["metadata".to_string(), "name".to_string()]),
ValueKind::Scalar,
vec![Guard::Default {
path: helm_schema_core::ValuesPath::parse("serviceAccount.name"),
}],
Some(resource),
),
]);
sim_assert_eq!(
have: signals
.evidence_for(&conditional_path("podLabels"))
.map(|evidence| &evidence.metadata_field_kinds),
want: Some(&BTreeSet::from([MetadataFieldKind::StringMap])),
);
assert!(
signals
.evidence_for(&conditional_path("serviceAccount.name"))
.is_some_and(|evidence| evidence.facts.is_nullable),
"default-guarded render use should surface as nullable contract evidence",
);
assert!(
signals
.evidence_for(&conditional_path("serviceAccount"))
.is_some_and(|evidence| evidence.facts.has_referenced_descendants),
"contract schema signals should own descendant path topology",
);
assert!(
signals
.evidence_for(&conditional_path("serviceAccount.name"))
.is_some_and(|evidence| evidence.facts.has_render_use
&& evidence.facts.all_render_uses_self_guarded.holds()),
"contract value-path facts should own render-use evidence",
);
assert!(
signals
.evidence_for(&conditional_path("serviceAccount"))
.is_some_and(|evidence| evidence.facts.has_referenced_descendants),
"contract value-path facts should own descendant path topology",
);
assert!(
signals
.evidence_for(&conditional_path("serviceAccount.name"))
.is_some_and(|evidence| evidence.facts.has_render_use
&& evidence.facts.all_render_uses_self_guarded.holds()
&& evidence.facts.is_nullable),
"contract value-path facts should bundle nullable render-use evidence",
);
let pod_labels_evidence = signals
.evidence_for(&conditional_path("podLabels"))
.expect("podLabels evidence");
sim_assert_eq!(
have: pod_labels_evidence.metadata_field_kinds,
want: BTreeSet::from([MetadataFieldKind::StringMap]),
"path evidence should carry metadata lowering facts",
);
sim_assert_eq!(
have: pod_labels_evidence.provider_schema_uses.len(),
want: 1,
"path evidence should carry provider-schema requests for that path only",
);
let service_account_evidence = signals
.evidence_for(&conditional_path("serviceAccount.name"))
.expect("serviceAccount.name evidence");
assert!(service_account_evidence.is_referenced_value_path);
assert!(
service_account_evidence.facts.has_render_use
&& service_account_evidence
.facts
.all_render_uses_self_guarded
.holds()
&& service_account_evidence.facts.is_nullable,
"path evidence should carry render/nullability facts",
);
let service_account_parent_evidence = signals
.evidence_for(&conditional_path("serviceAccount"))
.expect("serviceAccount parent evidence");
assert!(
!service_account_parent_evidence.is_referenced_value_path,
"ancestor-only fact rows must not become schema subjects",
);
sim_assert_eq!(have: provider_schema_uses_for(&signals).len(), want: 2);
}
#[test]
fn contract_ir_conditional_path_overlays_capture_single_supported_guard_set() {
let signals = signals_for(vec![ContractUse::new(
helm_schema_core::ValuesPath::parse("feature.host"),
YamlPath(vec!["spec".to_string(), "host".to_string()]),
ValueKind::Scalar,
vec![
Guard::With {
path: helm_schema_core::ValuesPath::parse("feature"),
},
Guard::Truthy {
path: helm_schema_core::ValuesPath::parse("feature.enabled"),
},
],
None,
)]);
let overlays = conditional_overlays_for(&signals);
let overlay = overlays.first().expect("expected conditional overlay");
sim_assert_eq!(
have: overlay.target_value_path,
want: conditional_path("feature.host"),
"overlay should stay keyed by the values path being lowered"
);
sim_assert_eq!(
have: overlay.guards,
want: vec![
ConditionalGuard::Truthy {
path: conditional_path("feature.enabled"),
},
ConditionalGuard::With {
path: conditional_path("feature"),
},
],
);
assert!(
overlay.evidence.provider_schema_uses.is_empty(),
"non-resource scalar overlays should not invent provider lookups"
);
assert!(
overlay.evidence.metadata_field_kinds.is_empty(),
"non-metadata target should not carry metadata-field lowering hints"
);
sim_assert_eq!(
have: overlay.evidence.facts.has_render_use,
want: true,
"branch-local facts should preserve the target's render-use status"
);
}
#[test]
fn contract_ir_conditional_path_overlays_ignore_self_default_guards_beside_boolean_guards() {
let signals = signals_for(vec![ContractUse::new(
helm_schema_core::ValuesPath::parse("serviceAccount.name"),
YamlPath(vec!["metadata".to_string(), "name".to_string()]),
ValueKind::Scalar,
vec![
Guard::Truthy {
path: helm_schema_core::ValuesPath::parse("serviceAccount.create"),
},
Guard::Default {
path: helm_schema_core::ValuesPath::parse("serviceAccount.name"),
},
],
None,
)]);
let overlays = conditional_overlays_for(&signals);
let overlay = overlays.first().expect("expected conditional overlay");
sim_assert_eq!(
have: overlay.guards,
want: vec![ConditionalGuard::Truthy {
path: conditional_path("serviceAccount.create"),
}],
"self-default guards should not suppress an otherwise lowerable boolean branch",
);
assert!(
overlay.evidence.facts.is_nullable,
"branch-local nullability should still reflect the self-defaulted render use",
);
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "the complete overlay scenario is clearest as one contiguous test"
)]
fn contract_ir_conditional_path_overlays_preserve_values_decidable_not_and_or() {
let signals = signals_for(vec![
ContractUse::new(
helm_schema_core::ValuesPath::parse("feature.host"),
YamlPath(vec!["spec".to_string(), "host".to_string()]),
ValueKind::Scalar,
vec![Guard::Not {
path: helm_schema_core::ValuesPath::parse("feature.enabled"),
}],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("other.host"),
YamlPath(vec!["spec".to_string(), "other".to_string()]),
ValueKind::Scalar,
vec![Guard::Or {
paths: vec![
helm_schema_core::ValuesPath::parse("first.enabled"),
helm_schema_core::ValuesPath::parse("second.enabled"),
],
}],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("preset.resources"),
YamlPath(vec!["spec".to_string(), "resources".to_string()]),
ValueKind::Fragment,
vec![Guard::NotEq {
path: helm_schema_core::ValuesPath::parse("resourcesPreset"),
value: GuardValue::string("none"),
}],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("image.tag"),
YamlPath(vec!["spec".to_string(), "image".to_string()]),
ValueKind::Scalar,
vec![Guard::AnyOf {
alternatives: vec![
vec![
Guard::Truthy {
path: helm_schema_core::ValuesPath::parse("image.enabled"),
},
Guard::Eq {
path: helm_schema_core::ValuesPath::parse("image.mode"),
value: GuardValue::string("managed"),
},
],
vec![Guard::Not {
path: helm_schema_core::ValuesPath::parse("global.imageDisabled"),
}],
],
}],
None,
),
]);
let overlays = conditional_overlays_for(&signals);
sim_assert_eq!(have: overlays.len(), want: 4);
let feature_overlay = overlays
.iter()
.find(|overlay| overlay.target_value_path == conditional_path("feature.host"))
.expect("feature.host overlay");
let other_overlay = overlays
.iter()
.find(|overlay| overlay.target_value_path == conditional_path("other.host"))
.expect("other.host overlay");
let preset_overlay = overlays
.iter()
.find(|overlay| overlay.target_value_path == conditional_path("preset.resources"))
.expect("preset.resources overlay");
let image_overlay = overlays
.iter()
.find(|overlay| overlay.target_value_path == conditional_path("image.tag"))
.expect("image.tag overlay");
sim_assert_eq!(
have: feature_overlay.guards,
want: vec![ConditionalGuard::Not(Box::new(ConditionalGuard::Truthy {
path: conditional_path("feature.enabled"),
}))],
);
sim_assert_eq!(
have: other_overlay.guards,
want: vec![ConditionalGuard::AnyOf(vec![
ConditionalGuard::Truthy {
path: conditional_path("first.enabled"),
},
ConditionalGuard::Truthy {
path: conditional_path("second.enabled"),
},
])],
);
sim_assert_eq!(
have: preset_overlay.guards,
want: vec![ConditionalGuard::NotEq {
path: conditional_path("resourcesPreset"),
value: GuardValue::string("none"),
}],
);
sim_assert_eq!(
have: image_overlay.guards,
want: vec![ConditionalGuard::AnyOf(vec![
ConditionalGuard::Not(Box::new(ConditionalGuard::Truthy {
path: conditional_path("global.imageDisabled"),
})),
ConditionalGuard::AllOf(vec![
ConditionalGuard::Truthy {
path: conditional_path("image.enabled"),
},
ConditionalGuard::Eq {
path: conditional_path("image.mode"),
value: GuardValue::string("managed"),
},
]),
])],
);
assert!(
overlays.iter().all(|overlay| !overlay.preserve_base_schema),
"guarded-only branches must not preserve branch-specific evidence on the global base path: {overlays:?}"
);
}
#[test]
fn contract_ir_conditional_path_overlays_preserve_multiple_guarded_variants_per_path() {
let signals = signals_for(vec![
ContractUse::new(
helm_schema_core::ValuesPath::parse("feature.value"),
YamlPath(vec!["metadata".to_string(), "name".to_string()]),
ValueKind::Scalar,
vec![Guard::Eq {
path: helm_schema_core::ValuesPath::parse("mode"),
value: GuardValue::string("name"),
}],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("feature.value"),
YamlPath(vec!["metadata".to_string(), "labels".to_string()]),
ValueKind::Fragment,
vec![Guard::Eq {
path: helm_schema_core::ValuesPath::parse("mode"),
value: GuardValue::string("labels"),
}],
None,
),
]);
let overlays = conditional_overlays_for(&signals);
sim_assert_eq!(
have: overlays.len(),
want: 2,
"multiple supported guard sets for the same values path should survive as separate overlays"
);
assert!(
overlays.iter().any(|overlay| {
overlay.guards
== vec![ConditionalGuard::Eq {
path: conditional_path("mode"),
value: GuardValue::string("name"),
}]
&& overlay.evidence.metadata_field_kinds
== BTreeSet::from([MetadataFieldKind::Name])
}),
"expected a metadata.name-targeted branch overlay"
);
assert!(
overlays.iter().any(|overlay| {
overlay.guards
== vec![ConditionalGuard::Eq {
path: conditional_path("mode"),
value: GuardValue::string("labels"),
}]
&& overlay.evidence.metadata_field_kinds
== BTreeSet::from([MetadataFieldKind::StringMap])
&& overlay.evidence.facts.used_as_fragment
}),
"expected a metadata.labels fragment branch overlay"
);
}
#[test]
fn contract_ir_unconditional_use_subsumes_matching_guarded_overlay() {
let signals = signals_for(vec![
ContractUse::new(
helm_schema_core::ValuesPath::parse("feature.host"),
YamlPath(vec!["spec".to_string(), "host".to_string()]),
ValueKind::Scalar,
vec![Guard::Truthy {
path: helm_schema_core::ValuesPath::parse("feature.enabled"),
}],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("feature.host"),
YamlPath(vec!["spec".to_string(), "host".to_string()]),
ValueKind::Scalar,
Vec::new(),
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("other.path"),
YamlPath(vec!["spec".to_string(), "other".to_string()]),
ValueKind::Scalar,
vec![Guard::Range {
path: helm_schema_core::ValuesPath::parse("other.items"),
}],
None,
),
]);
let overlays = conditional_overlays_for(&signals);
sim_assert_eq!(
have: overlays.len(),
want: 0,
"the guarded use adds no evidence beyond the identical unconditional use: {:?}",
overlays
);
assert!(
signals
.schema_evidence_by_value_path()
.get(&conditional_path("feature.host"))
.is_some_and(|evidence| evidence.facts.has_unconditional_render_use),
"the surviving use should remain unconditional",
);
assert!(
!overlays
.iter()
.any(|overlay| overlay.target_value_path == conditional_path("other.path")),
"unsupported range-guarded paths must still stay on the wide/base path: {overlays:?}"
);
}
#[test]
fn contract_ir_conditional_path_overlays_drop_base_only_for_complete_boolean_partition() {
let signals = signals_for(vec![
ContractUse::new(
helm_schema_core::ValuesPath::parse("feature.host"),
YamlPath(vec!["spec".to_string(), "host".to_string()]),
ValueKind::Scalar,
vec![
Guard::Truthy {
path: helm_schema_core::ValuesPath::parse("feature.enabled"),
},
Guard::Truthy {
path: helm_schema_core::ValuesPath::parse("app.enabled"),
},
],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("feature.host"),
YamlPath(vec!["spec".to_string(), "host".to_string()]),
ValueKind::Scalar,
vec![
Guard::Truthy {
path: helm_schema_core::ValuesPath::parse("feature.enabled"),
},
Guard::Not {
path: helm_schema_core::ValuesPath::parse("app.enabled"),
},
],
None,
),
]);
let overlays = conditional_overlays_for(&signals);
sim_assert_eq!(
have: overlays.len(),
want: 1,
"complementary equal-evidence branches should resolve into one: {:?}",
overlays
);
sim_assert_eq!(
have: overlays[0].guards,
want: vec![ConditionalGuard::Truthy {
path: conditional_path("feature.enabled"),
}],
);
assert!(
overlays.iter().all(|overlay| !overlay.preserve_base_schema),
"a complete truthy/not partition should be allowed to replace the base schema entirely: {overlays:?}"
);
}
#[test]
fn contract_ir_conditional_path_overlays_drop_base_for_partition_with_common_prefix_branch() {
let signals = signals_for(vec![
ContractUse::new(
helm_schema_core::ValuesPath::parse("feature.host"),
YamlPath(vec!["spec".to_string(), "host".to_string()]),
ValueKind::Scalar,
vec![Guard::Truthy {
path: helm_schema_core::ValuesPath::parse("feature.enabled"),
}],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("feature.host"),
YamlPath(vec!["spec".to_string(), "host".to_string()]),
ValueKind::Scalar,
vec![
Guard::Truthy {
path: helm_schema_core::ValuesPath::parse("feature.enabled"),
},
Guard::Truthy {
path: helm_schema_core::ValuesPath::parse("app.enabled"),
},
],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("feature.host"),
YamlPath(vec!["spec".to_string(), "host".to_string()]),
ValueKind::Scalar,
vec![
Guard::Truthy {
path: helm_schema_core::ValuesPath::parse("feature.enabled"),
},
Guard::Not {
path: helm_schema_core::ValuesPath::parse("app.enabled"),
},
],
None,
),
]);
let overlays = conditional_overlays_for(&signals);
sim_assert_eq!(
have: overlays.len(),
want: 1,
"the partition should collapse into the broad shared branch: {:?}",
overlays
);
sim_assert_eq!(
have: overlays[0].guards,
want: vec![ConditionalGuard::Truthy {
path: conditional_path("feature.enabled"),
}],
);
assert!(
overlays.iter().all(|overlay| !overlay.preserve_base_schema),
"a shared broad branch plus a truthy/not partition should still replace the base schema: {overlays:?}"
);
}
#[test]
fn contract_ir_derives_schema_signals_without_projection_detour() {
let resource = ResourceRef::concrete("v1".to_string(), "ServiceAccount".to_string());
let mut contract = ContractIr::default();
contract.push(ContractUse::new(
helm_schema_core::ValuesPath::parse("serviceAccount.name"),
YamlPath(vec!["metadata".to_string(), "name".to_string()]),
ValueKind::Scalar,
Vec::new(),
Some(resource.clone()),
));
contract.push(ContractUse::new(
helm_schema_core::ValuesPath::parse("serviceAccount.name"),
YamlPath(vec!["metadata".to_string(), "name".to_string()]),
ValueKind::Scalar,
vec![Guard::Default {
path: helm_schema_core::ValuesPath::parse("serviceAccount.name"),
}],
Some(resource),
));
contract.push(ContractUse::new(
helm_schema_core::ValuesPath::parse("podLabels"),
YamlPath(vec!["metadata".to_string(), "labels".to_string()]),
ValueKind::Fragment,
Vec::new(),
None,
));
let direct_signals = contract.finalize().into_schema_signals();
assert!(
direct_signals
.evidence_for(&conditional_path("serviceAccount.name"))
.is_some_and(|evidence| evidence.facts.is_nullable),
"semantic finalization should keep the default-guarded render claim",
);
sim_assert_eq!(have: provider_schema_uses_for(&direct_signals).len(), want: 1);
assert!(
direct_signals
.evidence_for(&conditional_path("podLabels"))
.is_some_and(|evidence| evidence
.metadata_field_kinds
.contains(&MetadataFieldKind::StringMap)),
);
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "the complete requiredness scenario is clearest as one contiguous test"
)]
fn contract_ir_requiredness_evidence_is_path_local() {
let signals = ContractIr::from_contract_uses(vec![
ContractUse::new(
helm_schema_core::ValuesPath::parse("feature.enabled"),
YamlPath(Vec::new()),
ValueKind::Scalar,
vec![Guard::Truthy {
path: helm_schema_core::ValuesPath::parse("feature.enabled"),
}],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("mode"),
YamlPath(Vec::new()),
ValueKind::Scalar,
vec![Guard::Eq {
path: helm_schema_core::ValuesPath::parse("mode"),
value: GuardValue::string("strict"),
}],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("optional"),
YamlPath(Vec::new()),
ValueKind::Scalar,
vec![Guard::Not {
path: helm_schema_core::ValuesPath::parse("optional"),
}],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("resourcesPreset"),
YamlPath(Vec::new()),
ValueKind::Scalar,
vec![Guard::NotEq {
path: helm_schema_core::ValuesPath::parse("resourcesPreset"),
value: GuardValue::string("none"),
}],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("either.primary"),
YamlPath(vec!["metadata".to_string(), "name".to_string()]),
ValueKind::Scalar,
vec![Guard::Or {
paths: vec![
helm_schema_core::ValuesPath::parse("either.primary"),
helm_schema_core::ValuesPath::parse("either.fallback"),
],
}],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("ranged"),
YamlPath(vec!["spec".to_string(), "ports".to_string()]),
ValueKind::Scalar,
vec![Guard::Range {
path: helm_schema_core::ValuesPath::parse("ranged"),
}],
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("defaulted"),
YamlPath(vec!["metadata".to_string(), "labels".to_string()]),
ValueKind::Scalar,
vec![Guard::Default {
path: helm_schema_core::ValuesPath::parse("defaulted"),
}],
None,
),
])
.finalize()
.into_schema_signals();
let evidence = signals.schema_evidence_by_value_path();
assert!(
evidence
.get(&conditional_path("feature.enabled"))
.is_some_and(|evidence| evidence.requiredness.is_positive_header)
);
assert!(
evidence
.get(&conditional_path("mode"))
.is_some_and(|evidence| evidence.requiredness.is_positive_header)
);
assert!(
evidence
.get(&conditional_path("optional"))
.is_some_and(|evidence| evidence.requiredness.is_conditionally_optional)
);
assert!(
evidence
.get(&conditional_path("resourcesPreset"))
.is_some_and(|evidence| evidence.requiredness.is_conditionally_optional)
);
assert!(
evidence
.get(&conditional_path("either.primary"))
.is_some_and(|evidence| evidence.requiredness.is_conditionally_optional)
);
assert!(
evidence
.get(&conditional_path("either.fallback"))
.is_some_and(|evidence| evidence.requiredness.is_conditionally_optional)
);
assert!(
evidence
.get(&conditional_path("defaulted"))
.is_some_and(|evidence| evidence.requiredness.has_default_fallback)
);
assert!(
evidence
.get(&conditional_path("ranged"))
.is_some_and(|evidence| !evidence.requiredness.is_positive_header)
);
}
#[test]
fn contract_ir_requiredness_evidence_ignores_pathless_scalar_non_headers() {
let signals = ContractIr::from_contract_uses(vec![
ContractUse::new(
helm_schema_core::ValuesPath::parse("rendered.value"),
YamlPath(Vec::new()),
ValueKind::Scalar,
Vec::new(),
None,
),
ContractUse::new(
helm_schema_core::ValuesPath::parse("helper.dependency"),
YamlPath(Vec::new()),
ValueKind::Scalar,
vec![Guard::With {
path: helm_schema_core::ValuesPath::parse("helper.scope"),
}],
None,
),
])
.finalize()
.into_schema_signals();
assert!(
signals
.schema_evidence_by_value_path()
.values()
.all(|evidence| !evidence.requiredness.is_positive_header),
"plain pathless scalar uses must not be treated as positive header facts: {:#?}",
signals.schema_evidence_by_value_path()
);
sim_assert_eq!(
have: signals
.schema_evidence_by_value_path()
.iter()
.filter(|(path, _)| {
matches!(path.encode().as_str(), "helper.dependency" | "rendered.value")
})
.map(|(path, evidence)| (path.clone(), evidence.facts.has_non_control_use))
.collect::<Vec<_>>(),
want: vec![
(conditional_path("helper.dependency"), false),
(conditional_path("rendered.value"), false),
]
);
}
#[test]
fn widened_dependencies_only_admit_paths_beneath_closed_roots() -> eyre::Result<()> {
let signals = signals_for(vec![ContractUse::new(
helm_schema_core::ValuesPath::parse("guard.deep.flag"),
YamlPath(Vec::new()),
ValueKind::WidenedDependency,
Vec::new(),
None,
)]);
let evidence = signals
.evidence_for(&conditional_path("guard.deep.flag"))
.ok_or_eyre("widened dependency evidence")?;
assert!(
signals
.referenced_value_paths()
.contains(&conditional_path("guard.deep.flag")),
"the dependency must keep its path admitted beneath a closed root"
);
assert!(
!evidence.facts.has_non_control_use
&& !evidence.facts.used_as_yaml_serialized
&& !evidence.facts.used_as_fragment
&& evidence.provider_schema_uses.is_empty(),
"the widened dependency must not masquerade as a render fact: {evidence:#?}"
);
Ok(())
}
#[test]
fn unsupported_conditional_row_does_not_promote_sink_evidence() {
let signals = signals_for_template(indoc! {r"
{{- if mystery .Values.version }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Values.name }}
{{- end }}
"});
assert!(
signals
.evidence_for(&conditional_path("name"))
.is_none_or(|evidence| evidence.provider_schema_uses.is_empty()),
"a sink hidden behind an unlowerable condition cannot constrain the global path: {:#?}",
signals.schema_evidence_by_value_path(),
);
assert!(
signals
.evidence_for(&conditional_path("name"))
.is_none_or(|evidence| evidence.metadata_field_kinds.is_empty()),
"branch-local metadata typing cannot escape an unlowerable condition: {:#?}",
signals.schema_evidence_by_value_path(),
);
assert!(
signals
.evidence_for(&conditional_path("name"))
.is_none_or(|evidence| evidence.conditional_overlays.is_empty()),
"an unlowerable condition cannot be represented as a conditional overlay: {:#?}",
signals.schema_evidence_by_value_path(),
);
}
#[test]
fn unlowerable_output_selection_does_not_claim_a_path_wide_string_consumer() -> eyre::Result<()> {
let signals = signals_for_template(indoc! {r#"
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
token: {{ printf "%q" .Values.alpha | default .Values.omega | b64enc }}
"#});
let evidence = signals
.evidence_for(&conditional_path("omega"))
.ok_or_eyre("omega evidence")?;
assert!(
!evidence.facts.has_non_self_guarded_string_contract,
"an unlowerable output selector must not become a path-wide raw consumer: {evidence:#?}"
);
Ok(())
}
#[test]
fn foreign_range_does_not_globalize_strict_consumer() {
let signals = signals_for_template(indoc! {r#"
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
keys: |
{{- range .Values.items }}
{{ keys $.Values.config | join "," }}
{{- end }}
"#});
let evidence = signals
.evidence_for(&conditional_path("config"))
.expect("strict consumer evidence");
assert!(
!evidence.requirement_implications.is_empty()
&& evidence.requirement_implications.iter().all(|implication| {
implication.outer_guards.iter().any(|guard| {
matches!(
guard,
helm_schema_core::ConditionalGuard::Truthy { path }
if path == &conditional_path("items")
)
})
}),
"a strict call that only executes inside a foreign range binds only behind \
the iteration's liveness (the body executed, so the direct collection is \
truthy), never globally: {evidence:#?}",
);
}
#[test]
fn nested_member_range_abstains_under_unlowerable_outer_guard() {
let signals = signals_for_template(indoc! {r"
{{- if mystery .Values.version }}
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
values: |
{{- range $group := .Values.groups }}
{{- range $item := $group }}
{{ $item }}
{{- end }}
{{- end }}
{{- end }}
"});
assert!(
signals
.evidence_for(&conditional_path("groups"))
.is_none_or(|evidence| {
!evidence.requirement_implications.iter().any(|implication| {
matches!(
implication.target,
helm_schema_core::ContractRequirementTarget::Members { .. }
)
})
}),
"a nested range cannot impose a member contract after its outer guard was lost: {:#?}",
signals.schema_evidence_by_value_path(),
);
}
#[test]
fn unlowerable_mixed_guard_retains_its_values_path_reference() {
let signals = signals_for_template(indoc! {r#"
{{- if .Values.alertmanager.enabled }}
{{- if .Values.alertmanager.ingress.enabled }}
{{- if and .Values.alertmanager.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
class: {{ .Values.alertmanager.ingress.className }}
{{- end }}
{{- end }}
{{- end }}
"#});
let evidence = signals
.evidence_for(&conditional_path("alertmanager.ingress.className"))
.unwrap_or_else(|| panic!("mixed guard path reference disappeared: {signals:#?}"));
assert!(
evidence.provider_schema_uses.is_empty(),
"the opaque semver arm must still block provider typing: {evidence:#?}"
);
}
#[test]
fn statically_false_capability_branch_contributes_no_body_evidence() {
let signals = signals_for_template_at_kubernetes_version(
indoc! {r#"
{{- if semverCompare "<1.6-0" .Capabilities.KubeVersion.GitVersion }}
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
dead: {{ toJson .Values.dead }}
{{- end }}
"#},
"1.35.0",
);
assert!(
signals.evidence_for(&conditional_path("dead")).is_none(),
"a branch excluded by the configured Kubernetes version must not contribute render evidence: {signals:#?}"
);
}
#[test]
fn statically_true_short_circuit_arm_keeps_its_values_execution_guard() -> eyre::Result<()> {
let source = indoc! {r#"
{{- if and .Values.tolerations (semverCompare "^1.6-0" .Capabilities.KubeVersion.GitVersion) }}
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
live: {{ toYaml .Values.tolerations | quote }}
{{- end }}
"#};
let defines = DefineIndex::new();
let finalized = SymbolicIrContext::with_policy(
&defines,
crate::SymbolicPolicy {
kubernetes_version: Some("1.35.0".to_string()),
..crate::SymbolicPolicy::default()
},
)
.generate_contract_ir(source)
.finalize();
let uses = finalized.uses().to_vec();
let signals = finalized.into_schema_signals();
let evidence = signals
.evidence_for(&conditional_path("tolerations"))
.ok_or_eyre("live branch lost its values evidence")?;
assert!(
evidence.facts.has_self_guarded_render_use
&& evidence.facts.all_render_uses_self_guarded.holds()
&& !evidence.facts.has_unconditional_render_use,
"the constant capability operand must not erase the preceding values execution guard: \
evidence={evidence:#?}; uses={uses:#?}"
);
Ok(())
}
#[test]
fn member_row_without_direct_range_identity_does_not_seed_schema_paths() {
let signals = signals_for(vec![ContractUse::new(
helm_schema_core::ValuesPath::parse("$sentinel.*"),
YamlPath(Vec::new()),
ValueKind::Scalar,
vec![Guard::Range {
path: helm_schema_core::ValuesPath::parse("$sentinel"),
}],
None,
)]);
assert!(
signals
.evidence_for(&conditional_path("$sentinel"))
.is_none()
);
assert!(
signals
.evidence_for(&conditional_path("$sentinel.*"))
.is_none()
);
}
#[test]
fn direct_ranged_nested_sentinel_retains_its_member_contract() {
let signals = signals_for_template(indoc! {r#"
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
rendered: |
{{- range $entry := .Values.entries }}
{{ tpl (get $entry "$tplYaml") $ }}
{{- end }}
"#});
let evidence = signals
.evidence_for(&conditional_path("entries"))
.unwrap_or_else(|| {
panic!(
"direct ranged sentinel member must survive: {:#?}",
signals.schema_evidence_by_value_path()
)
});
assert!(
evidence.requirement_implications.iter().any(|implication| {
implication.target
== helm_schema_core::ContractRequirementTarget::MembersAt {
target_path: vec!["$tplYaml".to_string()],
allow_integer: true,
}
&& implication.requirements
== vec![helm_schema_core::FailValueRequirement::SchemaType(
"string".to_string(),
)]
}),
"tpl must retain its scoped string contract on the nested sentinel: {evidence:#?}"
);
assert!(
signals
.evidence_for(&conditional_path("$tplYaml"))
.is_none()
);
assert!(
signals
.evidence_for(&conditional_path("$tplYaml.*"))
.is_none()
);
}
#[test]
fn get_on_destructured_range_value_requires_object_members() {
let signals = signals_for_template(indoc! {r#"
{{- range $name, $context := .Values.contexts }}
{{- $_ := get $context "creds" }}
{{- end }}
"#});
let evidence = signals
.evidence_for(&conditional_path("contexts"))
.expect("direct range evidence");
assert!(evidence.requirement_implications.iter().any(|implication| {
matches!(
implication.target,
helm_schema_core::ContractRequirementTarget::Members {
allow_integer: false
}
) && implication.requirements
== vec![helm_schema_core::FailValueRequirement::SchemaType(
"object".to_string(),
)]
}));
}
#[test]
fn unknown_member_access_site_makes_the_exact_domain_incomplete() -> eyre::Result<()> {
let signals = signals_for_template(indoc! {r#"
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
{{- if .Values.exact }}
exact: {{ .Values.host.first | quote }}
{{- end }}
{{- if (lookup "v1" "ConfigMap" "" "dynamic") }}
dynamic: {{ .Values.host.second | quote }}
{{- end }}
"#});
let evidence = signals
.evidence_for(&conditional_path("host"))
.ok_or_eyre("expected member-host evidence")?;
let completeness = evidence
.requirement_implications
.iter()
.flat_map(|implication| &implication.requirements)
.filter_map(|requirement| match requirement {
helm_schema_core::FailValueRequirement::MemberHost {
complete_domain, ..
} => Some(*complete_domain),
_ => None,
})
.collect::<Vec<_>>();
sim_assert_eq!(have: completeness, want: vec![false]);
Ok(())
}