use indoc::indoc;
use super::{
composed_instance, parse_ir, parse_ir_with_helpers, schema_accepts_instance,
schema_for_values_yaml,
};
#[test]
fn shadowed_merge_layer_binds_members_only_where_unshadowed() {
let src = indoc! {r"
{{- $ctx := merge (.Values.podSecurityContext | default dict) (.Values.securityContext | default dict) }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
spec:
template:
spec:
{{- with $ctx }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: main
"};
let schema = schema_for_values_yaml(
parse_ir(src),
Some(indoc! {"
podSecurityContext: {}
securityContext: {}
"}),
);
for (instance, want) in [
(
serde_json::json!({ "securityContext": { "runAsUser": { "bad": true } } }),
false,
),
(
serde_json::json!({ "securityContext": { "runAsUser": 1000 } }),
true,
),
(
serde_json::json!({
"podSecurityContext": { "runAsUser": 1000 },
"securityContext": { "runAsUser": { "bad": true } }
}),
true,
),
(
serde_json::json!({ "podSecurityContext": { "runAsUser": { "bad": true } } }),
false,
),
(
serde_json::json!({ "podSecurityContext": { "runAsUser": 1000 } }),
true,
),
(
serde_json::json!({ "securityContext": { "customExtra": "x" } }),
true,
),
(serde_json::json!({}), true),
] {
assert!(
schema_accepts_instance(&schema, &instance) == want,
"per-key merge shadowing scopes the legacy layer's typing: \
instance={instance}; want={want}; schema={schema}"
);
}
}
#[test]
fn merged_member_projection_reaches_both_layers() {
let src = indoc! {r#"
{{- $picked := pick (mergeOverwrite (deepCopy .Values.features) .Values.ctrl.featuresOverride) "logging" }}
{{- $flags := list -}}
{{- with $picked.logging -}}
{{- $flags = append $flags (print "--loggingFormat=" .format) -}}
{{- $flags = append $flags (print "--v=" .verbosity) -}}
{{- end -}}
apiVersion: v1
kind: Pod
metadata:
name: test
spec:
containers:
- name: test
image: busybox
args:
{{- range $flags }}
- {{ . }}
{{- end }}
"#};
let values = indoc! {"
features:
logging:
format: text
verbosity: 2
ctrl:
featuresOverride: {}
"};
let schema = schema_for_values_yaml(parse_ir(src), Some(values));
for (overrides, want, label) in [
(
serde_json::json!({ "features": { "logging": { "format": "json", "verbosity": 4 } } }),
true,
"map base logging",
),
(
serde_json::json!({
"ctrl": { "featuresOverride": { "logging": { "format": "json", "verbosity": 4 } } }
}),
true,
"map override logging",
),
(
serde_json::json!({ "features": { "logging": 5 } }),
false,
"scalar base unshadowed",
),
] {
let instance = composed_instance(values, overrides);
assert!(
schema_accepts_instance(&schema, &instance) == want,
"merged member projection {label}: instance={instance}; schema={schema}"
);
}
}
#[test]
fn merge_overwrite_reverses_layer_precedence() {
let src = indoc! {r"
{{- $ctx := mergeOverwrite (.Values.legacy | default dict) (.Values.preferred | default dict) }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
spec:
template:
spec:
{{- with $ctx }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: main
"};
let schema = schema_for_values_yaml(
parse_ir(src),
Some(indoc! {"
legacy: {}
preferred: {}
"}),
);
for (instance, want) in [
(
serde_json::json!({ "legacy": { "runAsUser": { "bad": true } } }),
false,
),
(
serde_json::json!({
"preferred": { "runAsUser": 1000 },
"legacy": { "runAsUser": { "bad": true } }
}),
true,
),
(
serde_json::json!({ "preferred": { "runAsUser": { "bad": true } } }),
false,
),
] {
assert!(
schema_accepts_instance(&schema, &instance) == want,
"mergeOverwrite flips which layer is shadowed: \
instance={instance}; want={want}; schema={schema}"
);
}
}
#[test]
fn fresh_dict_merge_layers_type_dynamic_members_with_shadow_refinement() {
let src = indoc! {r#"
{{- $ruleAnnotations := dig "MyAlert" (dict) .Values.additionalRuleAnnotations }}
{{- $groupAnnotations := default (dict) .Values.additionalRuleGroupAnnotations.mygroup }}
{{- $additionalAnnotations := mergeOverwrite (dict) $groupAnnotations $ruleAnnotations }}
apiVersion: v1
kind: ConfigMap
metadata:
name: test
{{- if $additionalAnnotations }}
annotations:
{{ toYaml $additionalAnnotations | indent 4 }}
{{- end }}
"#};
let values_yaml = indoc! {"
additionalRuleAnnotations: {}
additionalRuleGroupAnnotations: {}
"};
let schema = schema_for_values_yaml(parse_ir(src), Some(values_yaml));
for (overrides, want) in [
(
serde_json::json!({ "additionalRuleAnnotations": { "MyAlert": { "foo": 7 } } }),
false,
),
(
serde_json::json!({ "additionalRuleAnnotations": { "MyAlert": { "foo": "x" } } }),
true,
),
(
serde_json::json!({ "additionalRuleGroupAnnotations": { "mygroup": { "foo": 7 } } }),
false,
),
(
serde_json::json!({ "additionalRuleGroupAnnotations": { "mygroup": { "foo": "x" } } }),
true,
),
(
serde_json::json!({
"additionalRuleGroupAnnotations": { "mygroup": { "foo": 7 } },
"additionalRuleAnnotations": { "MyAlert": { "foo": "x" } }
}),
true,
),
(
serde_json::json!({
"additionalRuleGroupAnnotations": { "mygroup": { "foo": 7 } },
"additionalRuleAnnotations": { "MyAlert": { "bar": "x" } }
}),
true,
),
] {
let instance = composed_instance(values_yaml, overrides);
assert!(
schema_accepts_instance(&schema, &instance) == want,
"fresh-dict merge layers bind dynamic-member payloads per layer: \
instance={instance}; want={want}; schema={schema}"
);
}
}
#[test]
fn candidate_selection_helper_binds_provider_payload_through_scope_list() {
let helpers = indoc! {r#"
{{- define "test.podSecurityContext" }}
{{- $ := last . }}
{{- $result := dict }}
{{- range . }}
{{- if and (hasKey . "securityContexts") (hasKey .securityContexts "pod") .securityContexts.pod }}
{{- $result = .securityContexts.pod }}
{{- break }}
{{- end }}
{{- if and (hasKey . "securityContext") .securityContext }}
{{- $result = .securityContext }}
{{- break }}
{{- end }}
{{- end }}
{{- if $result }}
{{- toYaml $result | print }}
{{- else }}
runAsUser: {{ $.uid }}
{{- end }}
{{- end }}
"#};
let src = indoc! {r#"
{{- $securityContext := include "test.podSecurityContext" (list .Values.workers .Values) }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
spec:
template:
spec:
securityContext: {{ $securityContext | nindent 8 }}
containers:
- name: main
"#};
let schema = schema_for_values_yaml(
parse_ir_with_helpers(src, helpers),
Some(indoc! {"
uid: 50000
workers:
securityContexts: {}
securityContext: {}
securityContexts: {}
securityContext: {}
"}),
);
for (instance, want) in [
(
serde_json::json!({ "workers": { "securityContexts": { "pod": { "runAsUser": "oops" } } } }),
false,
),
(
serde_json::json!({ "workers": { "securityContexts": { "pod": { "runAsUser": 50000 } } } }),
true,
),
] {
assert!(
schema_accepts_instance(&schema, &instance) == want,
"scope-list candidate selection keeps provider member typing: \
instance={instance}; want={want}; schema={schema}"
);
}
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "the complete fixture scenario is clearest as one contiguous test"
)]
fn nil_scrubbed_merge_helper_layers_bind_candidate_provider_payloads() {
let helpers = indoc! {r#"
{{- define "test.podSecurityContext" }}
{{- $ := last . }}
{{- $result := dict }}
{{- range . }}
{{- if and (hasKey . "securityContexts") (hasKey .securityContexts "pod") .securityContexts.pod }}
{{- $result = .securityContexts.pod }}
{{- break }}
{{- end }}
{{- if and (hasKey . "securityContext") .securityContext }}
{{- $result = .securityContext }}
{{- break }}
{{- end }}
{{- end }}
{{- if $result }}
{{- toYaml $result | print }}
{{- else }}
runAsUser: {{ $.uid }}
{{- end }}
{{- end }}
{{- define "removeNilFields" -}}
{{- $newValues := dict -}}
{{- range $key, $val := . -}}
{{- if kindIs "map" $val -}}
{{- $nested := include "removeNilFields" $val | fromYaml -}}
{{- if gt (len $nested) 0 -}}
{{- $_ := set $newValues $key $nested -}}
{{- end -}}
{{- else if not (kindIs "invalid" $val) -}}
{{- $_ := set $newValues $key $val -}}
{{- end -}}
{{- end -}}
{{- toYaml $newValues -}}
{{- end -}}
{{- define "workersMergeValues" -}}
{{- $inputMap := index . 0 -}}
{{- $overwriteMap := index . 1 -}}
{{- $sectionName := index . 2 -}}
{{- $orBoolean := index . 3 -}}
{{- $outputMap := dict -}}
{{- $fullOverwrite := list "annotations" "podAnnotations" "securityContext" "resources" "nodeSelector" "affinity" "labels" -}}
{{- range $key, $val := $inputMap -}}
{{- if and (hasKey $overwriteMap $key) (has $key $fullOverwrite) -}}
{{- $_ := set $outputMap $key (get $overwriteMap $key) -}}
{{- else if and (hasKey $overwriteMap $key) (kindIs "map" $val) -}}
{{- $nested := include "workersMergeValues" (list $val (get $overwriteMap $key) $key $orBoolean) | fromYaml -}}
{{- if gt (len $nested) 0 -}}
{{- $_ := set $outputMap $key $nested -}}
{{- end -}}
{{- else if and (hasKey $overwriteMap $key) (not (and (kindIs "slice" (get $overwriteMap $key)) (eq (len (get $overwriteMap $key)) 0))) -}}
{{- if and (kindIs "bool" $val) (has $sectionName $orBoolean) -}}
{{- $_ := set $outputMap $key (or $val (get $overwriteMap $key)) -}}
{{- else -}}
{{- $_ := set $outputMap $key (get $overwriteMap $key) -}}
{{- end -}}
{{- else -}}
{{- $_ := set $outputMap $key $val -}}
{{- end -}}
{{- end -}}
{{- range $key, $val := $overwriteMap -}}
{{- if not (hasKey $inputMap $key) -}}
{{- $_ := set $outputMap $key $val -}}
{{- end -}}
{{- end -}}
{{- toYaml $outputMap -}}
{{- end -}}
"#};
let src = indoc! {r#"
{{- $filteredCelery := include "removeNilFields" .Values.workers.celery | fromYaml -}}
{{- $workers := include "workersMergeValues" (list .Values.workers $filteredCelery "" list) | fromYaml -}}
{{- $securityContext := include "test.podSecurityContext" (list $workers .Values) }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
spec:
template:
spec:
securityContext: {{ $securityContext | nindent 8 }}
containers:
- name: main
"#};
let schema = schema_for_values_yaml(
parse_ir_with_helpers(src, helpers),
Some(indoc! {"
uid: 50000
workers:
securityContexts: {}
celery:
securityContexts: {}
securityContexts: {}
securityContext: {}
"}),
);
for (instance, want) in [
(
serde_json::json!({ "workers": {
"securityContexts": { "pod": { "runAsUser": "oops" } },
"celery": { "securityContexts": { "pod": { "runAsUser": 50000 } } } } }),
true,
),
(
serde_json::json!({ "workers": { "securityContexts": { "pod": { "runAsUser": "oops" } } } }),
false,
),
(
serde_json::json!({ "workers": { "securityContexts": { "pod": { "runAsUser": 50000 } } } }),
true,
),
(
serde_json::json!({ "workers": { "celery": { "securityContexts": { "pod": { "runAsUser": "oops" } } } } }),
false,
),
(
serde_json::json!({ "workers": { "celery": { "securityContexts": { "pod": { "runAsUser": 50000 } } } } }),
true,
),
(
serde_json::json!({ "workers": { "celery": { "securityContexts": { "pod": { "runAsUser": null, "fsGroup": 5 } } } } }),
true,
),
(
serde_json::json!({ "workers": { "celery": { "securityContexts": { "pod": { "runAsUser": null } } } } }),
true,
),
] {
assert!(
schema_accepts_instance(&schema, &instance) == want,
"scrubbed merge-helper layer polarity: instance={instance}; want={want}; schema={schema}"
);
}
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "the complete fixture scenario is clearest as one contiguous test"
)]
fn rerooted_worker_set_merges_keep_layered_provider_payloads() {
let helpers = indoc! {r#"
{{- define "test.podSecurityContext" }}
{{- $ := last . }}
{{- $result := dict }}
{{- range . }}
{{- if and (hasKey . "securityContexts") (hasKey .securityContexts "pod") .securityContexts.pod }}
{{- $result = .securityContexts.pod }}
{{- break }}
{{- end }}
{{- if and (hasKey . "securityContext") .securityContext }}
{{- $result = .securityContext }}
{{- break }}
{{- end }}
{{- end }}
{{- if $result }}
{{- toYaml $result | print }}
{{- else }}
runAsUser: {{ $.uid }}
fsGroup: {{ $.gid }}
{{- end }}
{{- end }}
{{- define "removeNilFields" -}}
{{- $newValues := dict -}}
{{- range $key, $val := . -}}
{{- if kindIs "map" $val -}}
{{- $nested := include "removeNilFields" $val | fromYaml -}}
{{- if gt (len $nested) 0 -}}
{{- $_ := set $newValues $key $nested -}}
{{- end -}}
{{- else if not (kindIs "invalid" $val) -}}
{{- $_ := set $newValues $key $val -}}
{{- end -}}
{{- end -}}
{{- toYaml $newValues -}}
{{- end -}}
{{- define "workersMergeValues" -}}
{{- $inputMap := index . 0 -}}
{{- $overwriteMap := index . 1 -}}
{{- $sectionName := index . 2 -}}
{{- $orBoolean := index . 3 -}}
{{- $outputMap := dict -}}
{{- $fullOverwrite := list "annotations" "podAnnotations" "securityContext" "resources" "nodeSelector" "affinity" "labels" -}}
{{- range $key, $val := $inputMap -}}
{{- if and (hasKey $overwriteMap $key) (has $key $fullOverwrite) -}}
{{- $_ := set $outputMap $key (get $overwriteMap $key) -}}
{{- else if and (hasKey $overwriteMap $key) (kindIs "map" $val) -}}
{{- $nested := include "workersMergeValues" (list $val (get $overwriteMap $key) $key $orBoolean) | fromYaml -}}
{{- if gt (len $nested) 0 -}}
{{- $_ := set $outputMap $key $nested -}}
{{- end -}}
{{- else if and (hasKey $overwriteMap $key) (not (and (kindIs "slice" (get $overwriteMap $key)) (eq (len (get $overwriteMap $key)) 0))) -}}
{{- if and (kindIs "bool" $val) (has $sectionName $orBoolean) -}}
{{- $_ := set $outputMap $key (or $val (get $overwriteMap $key)) -}}
{{- else -}}
{{- $_ := set $outputMap $key (get $overwriteMap $key) -}}
{{- end -}}
{{- else -}}
{{- $_ := set $outputMap $key $val -}}
{{- end -}}
{{- end -}}
{{- range $key, $val := $overwriteMap -}}
{{- if not (hasKey $inputMap $key) -}}
{{- $_ := set $outputMap $key $val -}}
{{- end -}}
{{- end -}}
{{- toYaml $outputMap -}}
{{- end -}}
"#};
let src = indoc! {r#"
{{- $globals := deepCopy . -}}
{{- $filteredCelery := include "removeNilFields" .Values.workers.celery | fromYaml -}}
{{- $mergedWorkers := include "workersMergeValues" (list .Values.workers $filteredCelery "" list) | fromYaml -}}
{{- $_ := unset $mergedWorkers "celery" -}}
{{- $workerSets := .Values.workers.celery.sets | default list -}}
{{- if .Values.workers.celery.enableDefault -}}
{{- $workerSets = concat (list (dict "name" "default")) $workerSets -}}
{{- end -}}
{{- range $workerSet := $workerSets -}}
{{- $workers := include "workersMergeValues" (list $mergedWorkers $workerSet "" list) | fromYaml -}}
{{- $_ := set $globals.Values "workers" $workers -}}
{{- with $globals -}}
{{- if or (contains "CeleryExecutor" .Values.executor) (contains "CeleryKubernetesExecutor" .Values.executor) }}
{{- $securityContext := include "test.podSecurityContext" (list .Values.workers .Values) }}
---
apiVersion: apps/v1
kind: {{ if .Values.workers.persistence.enabled }}StatefulSet{{ else }}Deployment{{ end }}
metadata:
name: test-{{ $workerSet.name }}
labels:
{{- if or .Values.labels .Values.workers.labels }}
{{- mustMerge .Values.workers.labels .Values.labels | toYaml | nindent 4 }}
{{- end }}
spec:
template:
spec:
securityContext: {{ $securityContext | nindent 8 }}
containers:
- name: main
{{- end }}
{{- end }}
{{- end }}
"#};
let values_yaml = indoc! {r#"
uid: 50000
gid: 0
executor: "CeleryExecutor"
securityContext: {}
securityContexts:
pod: {}
workers:
securityContext: {}
securityContexts:
pod: {}
container: {}
labels: {}
persistence:
enabled: false
celery:
enableDefault: true
sets: []
securityContext: {}
securityContexts:
pod: {}
container: {}
"#};
let schema = schema_for_values_yaml(parse_ir_with_helpers(src, helpers), Some(values_yaml));
for (overrides, want, label) in [
(
serde_json::json!({ "executor": "CeleryExecutor", "workers": {
"securityContexts": { "pod": { "runAsUser": "oops" } },
"celery": { "enableDefault": true } } }),
false,
"base-layer string runAsUser reaches the rendered pod context",
),
(
serde_json::json!({ "workers": {
"securityContexts": { "pod": { "runAsUser": 50000 } },
"celery": { "enableDefault": true } } }),
true,
"base-layer integer runAsUser renders",
),
(
serde_json::json!({ "executor": "CeleryExecutor", "workers": { "celery": {
"enableDefault": true,
"securityContexts": { "pod": { "runAsUser": "oops" } } } } }),
false,
"celery-layer string runAsUser reaches the rendered pod context",
),
(
serde_json::json!({ "workers": { "celery": {
"enableDefault": true,
"securityContexts": { "pod": { "runAsUser": null } } } } }),
true,
"the scrub drops null members before the sink",
),
(
serde_json::json!({ "workers": {
"securityContexts": { "pod": { "runAsUser": "oops" } },
"celery": {
"enableDefault": true,
"securityContexts": { "pod": { "runAsUser": 50000 } } } } }),
true,
"the celery layer shadows the base member",
),
(
serde_json::json!({ "executor": "CeleryExecutor", "workers": { "celery": { "sets": [
{ "name": "heavy", "labels": "oops" }
] } } }),
false,
"scalar labels in a worker set terminates the merge recursion",
),
(
serde_json::json!({ "workers": { "celery": { "sets": [
{ "name": "heavy", "labels": { "tier": "heavy" } }
] } } }),
true,
"map labels override renders",
),
(
serde_json::json!({ "workers": {
"securityContexts": { "pod": { "runAsUser": "oops" } },
"celery": { "enableDefault": false, "sets": [] } } }),
true,
"an empty worker family leaves the base layer unconsumed",
),
(
serde_json::json!({ "executor": "CeleryExecutor", "workers": {
"securityContexts": { "pod": { "runAsUser": "oops" } },
"celery": { "enableDefault": false, "sets": [ { "name": "heavy" } ] } } }),
false,
"a nonempty set list alone keeps the family live",
),
] {
let instance = composed_instance(values_yaml, overrides);
assert!(
schema_accepts_instance(&schema, &instance) == want,
"rerooted worker-set layers ({label}): instance={instance}; want={want}; schema={schema}"
);
}
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "the complete fixture scenario is clearest as one contiguous test"
)]
fn per_set_merge_layers_bind_without_the_reroot() {
let helpers = indoc! {r#"
{{- define "test.podSecurityContext" }}
{{- $ := last . }}
{{- $result := dict }}
{{- range . }}
{{- if and (hasKey . "securityContexts") (hasKey .securityContexts "pod") .securityContexts.pod }}
{{- $result = .securityContexts.pod }}
{{- break }}
{{- end }}
{{- end }}
{{- if $result }}
{{- toYaml $result | print }}
{{- else }}
runAsUser: {{ $.uid }}
{{- end }}
{{- end }}
{{- define "removeNilFields" -}}
{{- $newValues := dict -}}
{{- range $key, $val := . -}}
{{- if kindIs "map" $val -}}
{{- $nested := include "removeNilFields" $val | fromYaml -}}
{{- if gt (len $nested) 0 -}}
{{- $_ := set $newValues $key $nested -}}
{{- end -}}
{{- else if not (kindIs "invalid" $val) -}}
{{- $_ := set $newValues $key $val -}}
{{- end -}}
{{- end -}}
{{- toYaml $newValues -}}
{{- end -}}
{{- define "workersMergeValues" -}}
{{- $inputMap := index . 0 -}}
{{- $overwriteMap := index . 1 -}}
{{- $sectionName := index . 2 -}}
{{- $orBoolean := index . 3 -}}
{{- $outputMap := dict -}}
{{- $fullOverwrite := list "labels" "securityContext" "resources" -}}
{{- range $key, $val := $inputMap -}}
{{- if and (hasKey $overwriteMap $key) (has $key $fullOverwrite) -}}
{{- $_ := set $outputMap $key (get $overwriteMap $key) -}}
{{- else if and (hasKey $overwriteMap $key) (kindIs "map" $val) -}}
{{- $nested := include "workersMergeValues" (list $val (get $overwriteMap $key) $key $orBoolean) | fromYaml -}}
{{- if gt (len $nested) 0 -}}
{{- $_ := set $outputMap $key $nested -}}
{{- end -}}
{{- else if and (hasKey $overwriteMap $key) (not (and (kindIs "slice" (get $overwriteMap $key)) (eq (len (get $overwriteMap $key)) 0))) -}}
{{- $_ := set $outputMap $key (get $overwriteMap $key) -}}
{{- else -}}
{{- $_ := set $outputMap $key $val -}}
{{- end -}}
{{- end -}}
{{- range $key, $val := $overwriteMap -}}
{{- if not (hasKey $inputMap $key) -}}
{{- $_ := set $outputMap $key $val -}}
{{- end -}}
{{- end -}}
{{- toYaml $outputMap -}}
{{- end -}}
"#};
let src = indoc! {r#"
{{- $filteredCelery := include "removeNilFields" .Values.workers.celery | fromYaml -}}
{{- $mergedWorkers := include "workersMergeValues" (list .Values.workers $filteredCelery "" list) | fromYaml -}}
{{- $_ := unset $mergedWorkers "celery" -}}
{{- range $workerSet := .Values.workers.celery.sets -}}
{{- $workers := include "workersMergeValues" (list $mergedWorkers $workerSet "" list) | fromYaml -}}
{{- $securityContext := include "test.podSecurityContext" (list $workers $.Values) }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-{{ $workerSet.name }}
spec:
template:
spec:
securityContext: {{ $securityContext | nindent 8 }}
containers:
- name: main
{{- end }}
"#};
let schema = schema_for_values_yaml(
parse_ir_with_helpers(src, helpers),
Some(indoc! {"
uid: 50000
workers:
securityContexts: {}
celery:
sets: []
securityContexts: {}
"}),
);
for (instance, want, label) in [
(
serde_json::json!({ "workers": {
"securityContexts": { "pod": { "runAsUser": "oops" } },
"celery": { "sets": [ { "name": "a" } ] } } }),
false,
"base-layer string runAsUser",
),
(
serde_json::json!({ "workers": {
"securityContexts": { "pod": { "runAsUser": 50000 } },
"celery": { "sets": [ { "name": "a" } ] } } }),
true,
"base-layer integer runAsUser",
),
] {
assert!(
schema_accepts_instance(&schema, &instance) == want,
"per-set direct consumption ({label}): instance={instance}; want={want}; schema={schema}"
);
}
}
#[test]
fn selection_chain_merge_layers_keep_the_has_key_gated_splice() {
let src = indoc! {r#"
{{- if .Values.defaultRules.create }}
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: test
spec:
groups:
- name: alertmanager.rules
rules:
- alert: AlertmanagerFailedReload
annotations:
{{- $ruleAnnotations := dig "AlertmanagerFailedReload" (dict) .Values.defaultRules.additionalRuleAnnotations }}
{{- $groupAnnotations := default (dict) .Values.defaultRules.additionalRuleGroupAnnotations.alertmanager }}
{{- $additionalAnnotations := mergeOverwrite (dict) $groupAnnotations $ruleAnnotations }}
{{- if $additionalAnnotations }}
{{ toYaml $additionalAnnotations | indent 8 }}
{{- end }}
{{- if not (hasKey $additionalAnnotations "runbook_url") }}
runbook_url: {{ .Values.defaultRules.runbookUrl }}/alertmanager/alertmanagerfailedreload
{{- end }}
expr: vector(1)
{{- end }}
"#};
let values_yaml = indoc! {r#"
defaultRules:
create: true
additionalRuleAnnotations: {}
additionalRuleGroupAnnotations:
alertmanager: {}
runbookUrl: "https://runbooks.example/runbooks"
"#};
let schema = schema_for_values_yaml(parse_ir(src), Some(values_yaml));
for (overrides, want, label) in [
(
serde_json::json!({ "defaultRules": {
"create": true, "runbookUrl": [], "additionalRuleAnnotations": {} } }),
false,
"live array splice",
),
(
serde_json::json!({ "defaultRules": {
"create": true, "runbookUrl": "https://x", "additionalRuleAnnotations": {} } }),
true,
"live string splice",
),
(
serde_json::json!({ "defaultRules": {
"create": true, "runbookUrl": 7, "additionalRuleAnnotations": {} } }),
true,
"live integer splice",
),
(
serde_json::json!({ "defaultRules": {
"create": true,
"runbookUrl": [],
"additionalRuleAnnotations": {},
"additionalRuleGroupAnnotations": { "alertmanager": { "runbook_url": "x" } } } }),
true,
"group annotation shadows the splice",
),
(
serde_json::json!({ "defaultRules": {
"create": true,
"runbookUrl": [],
"additionalRuleAnnotations": { "AlertmanagerFailedReload": { "runbook_url": "x" } } } }),
true,
"rule annotation shadows the splice",
),
(
serde_json::json!({ "defaultRules": { "create": false, "runbookUrl": [] } }),
true,
"dormant rule document",
),
(serde_json::json!({}), true, "empty document"),
] {
let instance = composed_instance(values_yaml, overrides);
assert!(
schema_accepts_instance(&schema, &instance) == want,
"selection-chain merge layer presence ({label}): \
instance={instance}; want={want}; schema={schema}"
);
}
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "the complete fixture scenario is clearest as one contiguous test"
)]
fn dormant_component_gates_survive_their_wildcard_merge_layer() {
let helpers = indoc! {r#"
{{- define "test.containerSecurityContext" }}
{{- $ := last . }}
{{- $result := dict }}
{{- range . }}
{{- if and (hasKey . "securityContexts") (hasKey .securityContexts "container") .securityContexts.container }}
{{- $result = .securityContexts.container }}
{{- break }}
{{- end }}
{{- end }}
{{- if $result }}
{{- toYaml $result | print }}
{{- else }}
runAsUser: {{ $.uid }}
{{- end }}
{{- end }}
{{- define "removeNilFields" -}}
{{- $newValues := dict -}}
{{- range $key, $val := . -}}
{{- if kindIs "map" $val -}}
{{- $nested := include "removeNilFields" $val | fromYaml -}}
{{- if gt (len $nested) 0 -}}
{{- $_ := set $newValues $key $nested -}}
{{- end -}}
{{- else if not (kindIs "invalid" $val) -}}
{{- $_ := set $newValues $key $val -}}
{{- end -}}
{{- end -}}
{{- toYaml $newValues -}}
{{- end -}}
{{- define "workersMergeValues" -}}
{{- $inputMap := index . 0 -}}
{{- $overwriteMap := index . 1 -}}
{{- $sectionName := index . 2 -}}
{{- $orBoolean := index . 3 -}}
{{- $outputMap := dict -}}
{{- $fullOverwrite := list "annotations" "securityContext" "resources" -}}
{{- range $key, $val := $inputMap -}}
{{- if and (hasKey $overwriteMap $key) (has $key $fullOverwrite) -}}
{{- $_ := set $outputMap $key (get $overwriteMap $key) -}}
{{- else if and (hasKey $overwriteMap $key) (kindIs "map" $val) -}}
{{- $nested := include "workersMergeValues" (list $val (get $overwriteMap $key) $key $orBoolean) | fromYaml -}}
{{- if gt (len $nested) 0 -}}
{{- $_ := set $outputMap $key $nested -}}
{{- end -}}
{{- else if and (hasKey $overwriteMap $key) (not (and (kindIs "slice" (get $overwriteMap $key)) (eq (len (get $overwriteMap $key)) 0))) -}}
{{- if and (kindIs "bool" $val) (has $sectionName $orBoolean) -}}
{{- $_ := set $outputMap $key (or $val (get $overwriteMap $key)) -}}
{{- else -}}
{{- $_ := set $outputMap $key (get $overwriteMap $key) -}}
{{- end -}}
{{- else -}}
{{- $_ := set $outputMap $key $val -}}
{{- end -}}
{{- end -}}
{{- range $key, $val := $overwriteMap -}}
{{- if not (hasKey $inputMap $key) -}}
{{- $_ := set $outputMap $key $val -}}
{{- end -}}
{{- end -}}
{{- toYaml $outputMap -}}
{{- end -}}
"#};
let src = indoc! {r#"
{{- $globals := deepCopy . -}}
{{- $filteredCelery := include "removeNilFields" .Values.workers.celery | fromYaml -}}
{{- $mergedWorkers := include "workersMergeValues" (list .Values.workers $filteredCelery "" list) | fromYaml -}}
{{- $_ := unset $mergedWorkers "celery" -}}
{{- $workerSets := .Values.workers.celery.sets | default list -}}
{{- if .Values.workers.celery.enableDefault -}}
{{- $workerSets = concat (list (dict "name" "default")) $workerSets -}}
{{- end -}}
{{- range $workerSet := $workerSets -}}
{{- $workers := include "workersMergeValues" (list $mergedWorkers $workerSet "" list) | fromYaml -}}
{{- $_ := set $globals.Values "workers" $workers -}}
{{- with $globals -}}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-{{ $workerSet.name }}
spec:
template:
spec:
containers:
- name: main
{{- if .Values.workers.kerberosSidecar.enabled }}
- name: kerberos
securityContext: {{ include "test.containerSecurityContext" (list .Values.workers.kerberosSidecar .Values) | nindent 10 }}
{{- end }}
{{- end }}
{{- end }}
"#};
let values_yaml = indoc! {r"
uid: 50000
workers:
kerberosSidecar:
enabled: false
securityContexts:
container: {}
celery:
enableDefault: true
sets: []
kerberosSidecar:
enabled: ~
securityContexts:
container: {}
"};
let schema = schema_for_values_yaml(parse_ir_with_helpers(src, helpers), Some(values_yaml));
let junk = serde_json::json!({ "securityContexts": { "container": { "runAsUser": "oops" } } });
for (overrides, want, label) in [
(
serde_json::json!({ "workers": { "kerberosSidecar": junk } }),
true,
"every layer's gate is off, so the payload never renders",
),
(
serde_json::json!({ "workers": {
"kerberosSidecar": { "enabled": true,
"securityContexts": { "container": { "runAsUser": "oops" } } } } }),
false,
"the base layer's gate reaches the rendered container context",
),
(
serde_json::json!({ "workers": {
"kerberosSidecar": junk,
"celery": { "kerberosSidecar": { "enabled": true } } } }),
false,
"the celery layer's gate reaches it too",
),
(
serde_json::json!({ "workers": {
"kerberosSidecar": junk,
"celery": { "sets": [
{ "name": "heavy", "kerberosSidecar": { "enabled": true } }
] } } }),
false,
"a per-set layer can only supply the gate while sets has members",
),
] {
let instance = composed_instance(values_yaml, overrides);
assert!(
schema_accepts_instance(&schema, &instance) == want,
"dormant component gate ({label}): instance={instance}; want={want}; schema={schema}"
);
}
}