use super::*;
use indoc::indoc;
#[test]
fn step2_default_negative_integer_literal() {
let src = indoc! {r"
replicas: {{ default -3 .Values.replicas }}
"};
let hints = type_hints_for(parse_ir(src));
let schemas = hints.get("replicas").expect("replicas hint present");
assert!(
schemas.contains("integer"),
"expected integer hint for negative literal, got {schemas:?}"
);
}
#[test]
fn step2_default_rooted_values_paths_recognised() {
let src = indoc! {r#"
{{- range .Values.servers }}
name: {{ default "alertmanager" $.Values.alertmanager.nameOverride }}
alias: {{ default "main" $root.Values.alertmanager.aliasOverride }}
{{- end }}
"#};
let hints = type_hints_for(parse_ir(src));
assert!(
hints.contains_key("alertmanager.nameOverride"),
"expected hint for $.Values.alertmanager.nameOverride, got {hints:?}"
);
assert!(
hints.contains_key("alertmanager.aliasOverride"),
"expected hint for $root.Values.alertmanager.aliasOverride, got {hints:?}"
);
}
#[test]
fn step2_default_in_yaml_comment_no_hint() {
let src = indoc! {r#"
# example: {{ default "x" .Values.exampleName }}
name: actual
"#};
let hints = type_hints_for(parse_ir(src));
assert!(
hints.is_empty(),
"YAML comments must not produce hints, got {hints:?}"
);
}
#[test]
fn step2_default_in_helm_comment_no_hint() {
let src = indoc! {r#"
{{/* default "x" .Values.exampleName */}}
name: actual
"#};
let hints = type_hints_for(parse_ir(src));
assert!(
hints.is_empty(),
"Helm comments must not produce hints, got {hints:?}"
);
}
#[test]
fn step2_default_in_string_literal_no_hint() {
let src = indoc! {r#"
docs: {{- "see: default 5 .Values.example" | quote }}
"#};
let hints = type_hints_for(parse_ir(src));
assert!(
hints.is_empty(),
"Go-string-literal text must not produce hints, got {hints:?}"
);
}
#[test]
fn contract_ir_nullable_paths_require_all_render_uses_to_be_null_tolerant() {
let guarded = ContractUse {
source_expr: "image.tag".into(),
path: YamlPath(vec!["data".into(), "guarded".into()]),
kind: ValueKind::Scalar,
condition: helm_schema_core::GuardDnf::from_guards(vec![Guard::Default {
path: "image.tag".into(),
}]),
resource: None,
provenance: 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,
};
let bare = ContractUse {
source_expr: "image.tag".into(),
path: YamlPath(vec!["data".into(), "bare".into()]),
kind: ValueKind::Scalar,
condition: helm_schema_core::GuardDnf::from_guards(vec![]),
resource: None,
provenance: 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,
};
let signals = schema_signals_for(vec![guarded, bare]);
let null_paths = signals
.schema_evidence_by_value_path()
.iter()
.filter(|(_, evidence)| evidence.facts.is_nullable)
.map(|(path, _)| path.clone())
.collect::<BTreeSet<_>>();
assert!(
null_paths.is_empty(),
"image.tag must not be widened to nullable when one render use is unguarded; got {null_paths:?}",
);
}
#[test]
fn step2_default_in_helper_template_is_extracted() {
let helper_src = indoc! {r#"
{{- define "test.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{ default "default-name" .Values.serviceAccount.name }}
{{- end -}}
{{- end -}}
"#};
let hints = type_hints_for(parse_ir_with_helpers(
indoc! {r#"
name: {{ include "test.serviceAccountName" . }}
"#},
helper_src,
));
assert!(
hints.contains_key("serviceAccount.name"),
"expected hint for serviceAccount.name in helper, got {hints:?}"
);
}