use super::*;
#[test]
fn executing_else_member_access_closes_unmatched_scalar_domain() {
let src = indoc! {r#"
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
{{- if typeIs "string" .Values.provider }}
provider: {{ .Values.provider }}
{{- else }}
provider: {{ .Values.provider.name }}
{{- end }}
"#};
let values_yaml = "provider: aws\n";
let schema = schema_for_values_yaml(parse_ir(src), Some(values_yaml));
for instance in [
serde_json::json!({ "provider": "aws" }),
serde_json::json!({ "provider": { "name": "aws" } }),
] {
assert!(
schema_accepts_instance(&schema, &instance),
"both dispatch arms render: instance={instance}; schema={schema}"
);
}
assert!(
!schema_accepts_instance(&schema, &serde_json::json!({ "provider": 7 })),
"the else arm dereferences `.name`, so a non-string scalar fails \
rendering and must be rejected: {schema}"
);
}
#[test]
fn nested_type_dispatch_keeps_string_arm_under_active_outer_guards() {
let src = indoc! {r#"
{{- if .Values.monitoring.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
{{- if typeIs "string" .Values.image }}
image: {{ .Values.image }}
{{- else }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
{{- end }}
{{- end }}
"#};
let values_yaml = indoc! {"
monitoring:
enabled: false
image:
repository: repo
tag: latest
"};
let schema = schema_for_values_yaml(parse_ir(src), Some(values_yaml));
assert!(
schema_accepts_instance(
&schema,
&serde_json::json!({ "monitoring": { "enabled": true }, "image": "repo:1.2" })
),
"the string arm renders under active outer guards; the complement \
arm's object shape must not leak over it: {schema}"
);
assert!(
schema_accepts_instance(
&schema,
&serde_json::json!({
"monitoring": { "enabled": true },
"image": { "repository": "repo", "tag": "1.2" }
})
),
"the object arm stays valid under active outer guards: {schema}"
);
}
#[test]
fn with_rebound_dot_type_dispatch_binds_source_path() {
let src = indoc! {r#"
apiVersion: v1
kind: Pod
metadata:
name: test
spec:
containers:
{{- with .Values.extraContainers }}
{{- if eq (typeOf .) "string" }}
{{- tpl . $ | nindent 4 }}
{{- else }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
"#};
let values_yaml = "extraContainers: []\n";
let schema = schema_for_values_yaml(parse_ir(src), Some(values_yaml));
for instance in [
serde_json::json!({ "extraContainers": "- name: extra" }),
serde_json::json!({ "extraContainers": [{ "name": "extra" }] }),
] {
assert!(
schema_accepts_instance(&schema, &instance),
"string and structured arms both render: instance={instance}; schema={schema}"
);
}
assert!(
!schema_accepts_instance(&schema, &serde_json::json!({ "extraContainers": 7 })),
"a non-string scalar reaches the structural else placement and \
renders invalid YAML; it must be rejected: {schema}"
);
}
#[test]
fn partially_observed_selector_object_stays_open() {
let src = indoc! {r"
apiVersion: v1
kind: Pod
metadata:
name: test
spec:
volumes:
- name: creds
secret:
secretName: {{ .Values.credentials.secret.name }}
"};
let schema = schema_for_values_yaml(parse_ir(src), None);
assert!(
schema_accepts_instance(
&schema,
&serde_json::json!({
"credentials": { "secret": { "name": "secret-name", "key": "k" } }
})
),
"a member read must not close the selector object to observed keys: {schema}"
);
}
#[test]
fn serialized_declared_mapping_sections_stay_open() {
let src = indoc! {r"
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
config.yaml: |
{{- toYaml .Values.config | nindent 4 }}
"};
let values_yaml = indoc! {"
config:
server:
port: 80
"};
let schema = schema_for_values_yaml(parse_ir(src), Some(values_yaml));
assert!(
schema_accepts_instance(
&schema,
&serde_json::json!({
"config": {
"server": { "port": 80, "root_url": "http://x" },
"smtp": { "enabled": true, "host": "mail" }
}
})
),
"serialized passthrough config must accept keys beyond the declared \
default shape: {schema}"
);
}
#[test]
fn guard_read_beside_serialized_render_keeps_mapping_open() {
let src = indoc! {r"
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
{{- if .Values.config.server }}
port: {{ .Values.config.server.port }}
{{- end }}
config.yaml: |
{{- toYaml .Values.config | nindent 4 }}
"};
let values_yaml = indoc! {"
config:
server:
port: 80
"};
let schema = schema_for_values_yaml(parse_ir(src), Some(values_yaml));
assert!(
schema_accepts_instance(
&schema,
&serde_json::json!({
"config": { "server": { "port": 80, "extra": 1 }, "another": {} }
})
),
"a truthy/member read beside the serialized render must not close \
the mapping: {schema}"
);
}
#[test]
fn serialized_truthy_guarded_leaf_admits_arrays() {
let src = indoc! {r"
apiVersion: example.com/v1
kind: Widget
metadata:
name: test
spec:
{{- if .Values.service.clusterIPs }}
clusterIPs:
{{ toYaml .Values.service.clusterIPs | nindent 4 }}
{{- end }}
"};
let values_yaml = indoc! {"
service: {}
"};
let schema = schema_for_values_yaml(parse_ir(src), Some(values_yaml));
assert!(
schema_accepts_instance(
&schema,
&serde_json::json!({ "service": { "clusterIPs": ["10.96.0.10"] } })
),
"a serialized guarded leaf renders arrays; the schema must not pin \
it to object: {schema}"
);
}
#[test]
fn guarded_join_owns_descendant_beneath_preserved_member_host() {
let src = indoc! {r#"
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
{{- if eq (default "cookie" .Values.sessionStorage.type) "redis" }}
{{- if eq (default "" .Values.sessionStorage.redis.clientType) "sentinel" }}
urls: {{ join "," .Values.sessionStorage.redis.sentinel.connectionUrls | quote }}
{{- end }}
{{- end }}
"#};
let values_yaml = indoc! {"
sessionStorage:
type: cookie
redis:
clientType: standalone
sentinel:
connectionUrls: []
"};
let schema = schema_for_values_yaml(parse_ir(src), Some(values_yaml));
for connection_urls in [
serde_json::json!(["redis://one:26379", "redis://two:26379"]),
serde_json::json!("redis://one:26379,redis://two:26379"),
] {
let instance = serde_json::json!({
"sessionStorage": {
"type": "redis",
"redis": {
"clientType": "sentinel",
"sentinel": { "connectionUrls": connection_urls }
}
}
});
assert!(
schema_accepts_instance(&schema, &instance),
"`join` owns the active descendant shape beneath its preserved \
ancestor: instance={instance}; schema={schema}"
);
}
}
#[test]
fn flag_splice_accepts_any_scalar_beyond_declared_string() {
let src = indoc! {r"
apiVersion: v1
kind: Pod
metadata:
name: test
spec:
containers:
- name: main
args:
- -v={{ .Values.klogLevel }}
"};
let values_yaml = "klogLevel: \"\"\n";
let schema = schema_for_values_yaml(parse_ir(src), Some(values_yaml));
for instance in [
serde_json::json!({ "klogLevel": 8 }),
serde_json::json!({ "klogLevel": "8" }),
serde_json::json!({ "klogLevel": [8] }),
serde_json::json!({ "klogLevel": { "level": 8 } }),
] {
assert!(
schema_accepts_instance(&schema, &instance),
"flag splices format every input kind: instance={instance}; schema={schema}"
);
}
}
#[test]
fn declared_boolean_flag_splice_accepts_string_form() {
let src = indoc! {r"
apiVersion: v1
kind: Pod
metadata:
name: test
spec:
containers:
- name: main
args:
- --read-only={{ .Values.readOnly }}
"};
let values_yaml = "readOnly: false\n";
let schema = schema_for_values_yaml(parse_ir(src), Some(values_yaml));
for instance in [
serde_json::json!({ "readOnly": true }),
serde_json::json!({ "readOnly": "true" }),
] {
assert!(
schema_accepts_instance(&schema, &instance),
"a flag splice prints booleans and strings alike: instance={instance}; schema={schema}"
);
}
}
#[test]
fn quoted_string_slot_widen_declared_boolean_to_scalars() {
let src = indoc! {r#"
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: test
parameters:
archiveOnDelete: "{{ .Values.storageClass.archiveOnDelete }}"
"#};
let values_yaml = indoc! {"
storageClass:
archiveOnDelete: false
"};
let schema = schema_for_values_yaml(parse_ir(src), Some(values_yaml));
for instance in [
serde_json::json!({ "storageClass": { "archiveOnDelete": "false" } }),
serde_json::json!({ "storageClass": { "archiveOnDelete": false } }),
] {
assert!(
schema_accepts_instance(&schema, &instance),
"a quoted scalar slot prints booleans and strings alike: instance={instance}; schema={schema}"
);
}
}
#[test]
fn declared_empty_map_guarded_fragment_admits_arrays() {
let src = indoc! {r"
apiVersion: example.com/v1
kind: Widget
metadata:
name: test
spec:
{{- if .Values.additionalVolumes }}
volumes:
{{- toYaml .Values.additionalVolumes | nindent 4 }}
{{- end }}
"};
let values_yaml = "additionalVolumes: {}\n";
let schema = schema_for_values_yaml(parse_ir(src), Some(values_yaml));
for instance in [
serde_json::json!({ "additionalVolumes": [{ "name": "v" }] }),
serde_json::json!({ "additionalVolumes": { "v": { "hostPath": "/" } } }),
serde_json::json!({ "additionalVolumes": {} }),
] {
assert!(
schema_accepts_instance(&schema, &instance),
"toYaml fragments render lists and maps alike: instance={instance}; schema={schema}"
);
}
}
#[test]
fn with_dot_tpl_keeps_string_form_valid() {
let helper_src = indoc! {r#"
{{- define "test.env" }}
{{- with .Values.extraEnv }}
{{- tpl . $ | nindent 2 }}
{{- end }}
{{- end }}
"#};
let src = indoc! {r#"
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
env: |
{{- include "test.env" . | nindent 4 }}
"#};
let values_yaml = "extraEnv: ~\n";
let schema = schema_for_values_yaml(parse_ir_with_helpers(src, helper_src), Some(values_yaml));
for instance in [
serde_json::json!({ "extraEnv": "- name: FOO\n value: bar" }),
serde_json::json!({ "extraEnv": null }),
] {
assert!(
schema_accepts_instance(&schema, &instance),
"tpl consumes a template string; the string and declared-null \
forms stay valid: instance={instance}; schema={schema}"
);
}
assert!(
!schema_accepts_instance(&schema, &serde_json::json!({ "extraEnv": { "a": 1 } })),
"a truthy non-string reaches `tpl` and aborts rendering: {schema}"
);
}
#[test]
fn self_guarded_declared_object_accepts_explicit_null() {
let src = indoc! {r"
apiVersion: v1
kind: Pod
metadata:
name: test
spec:
{{- if .Values.securityContext }}
securityContext:
{{- toYaml .Values.securityContext | nindent 4 }}
{{- end }}
"};
let values_yaml = indoc! {"
securityContext:
runAsUser: 0
"};
let schema = schema_for_values_yaml(parse_ir(src), Some(values_yaml));
for instance in [
serde_json::json!({ "securityContext": null }),
serde_json::json!({ "securityContext": { "runAsUser": 0 } }),
] {
assert!(
schema_accepts_instance(&schema, &instance),
"helm null-deletion plus the falsy self-guard makes explicit \
null render fine: instance={instance}; schema={schema}"
);
}
}
#[test]
fn literal_key_dig_binds_intermediate_object_contract() {
let src = indoc! {r#"
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
value: {{ dig "section" "leaf" "fallback" .Values.cfg }}
"#};
let values_yaml = "cfg: {}\n";
let schema = schema_for_values_yaml(parse_ir(src), Some(values_yaml));
for instance in [
serde_json::json!({ "cfg": { "section": { "leaf": "seven" } } }),
serde_json::json!({ "cfg": {} }),
] {
assert!(
schema_accepts_instance(&schema, &instance),
"dig walks maps and falls back on missing keys: instance={instance}; schema={schema}"
);
}
assert!(
!schema_accepts_instance(&schema, &serde_json::json!({ "cfg": "scalar" })),
"sprig `dig` type-asserts its subject to a map; a truthy non-map \
aborts rendering: {schema}"
);
}
#[test]
fn include_condition_absorbs_helper_type_dispatch_alternatives() {
let helper_src = indoc! {r#"
{{- define "test.enabled" -}}
{{- if kindIs "map" .Values.autoscaling -}}
{{- if .Values.autoscaling.enabled -}}
true
{{- end -}}
{{- else if kindIs "string" .Values.autoscaling -}}
{{- .Values.autoscaling -}}
{{- end -}}
{{- end -}}
"#};
let src = indoc! {r#"
{{- if (include "test.enabled" .) }}
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
enabled: "yes"
{{- end }}
"#};
let values_yaml = indoc! {"
autoscaling:
enabled: false
"};
let schema = schema_for_values_yaml(parse_ir_with_helpers(src, helper_src), Some(values_yaml));
for instance in [
serde_json::json!({ "autoscaling": { "enabled": true } }),
serde_json::json!({ "autoscaling": "on" }),
] {
assert!(
schema_accepts_instance(&schema, &instance),
"the helper's kindIs dispatch proves both forms render: \
instance={instance}; schema={schema}"
);
}
}
#[test]
fn helper_sequence_continuation_requires_its_serialized_source() {
let helpers = indoc! {r#"
{{- define "pod" -}}
volumeMounts:
- name: config
mountPath: /config
{{- if eq .Values.kind "DaemonSet" }}
{{- toYaml .Values.daemonSetVolumeMounts | nindent 2 }}
{{- end }}
{{- end -}}
"#};
let src = indoc! {r#"
apiVersion: v1
kind: Pod
metadata:
name: test
spec:
containers:
- name: test
image: test
{{- include "pod" . | nindent 6 }}
"#};
let schema = schema_for_values_yaml(
parse_ir_with_helpers(src, helpers),
Some(indoc! {"
kind: DaemonSet
daemonSetVolumeMounts:
- name: varlog
mountPath: /var/log
"}),
);
for (instance, want, label) in [
(
serde_json::json!({
"kind": "DaemonSet",
"daemonSetVolumeMounts": [{ "name": "varlog", "mountPath": "/var/log" }]
}),
true,
"a live list continues the sequence",
),
(
serde_json::json!({ "kind": "DaemonSet" }),
false,
"a missing live source emits null beside the fixed item",
),
(
serde_json::json!({ "kind": "Deployment" }),
true,
"the inactive serialization leaves the source dormant",
),
] {
assert!(
schema_accepts_instance(&schema, &instance) == want,
"helper serialization scope ({label}): instance={instance}; want={want}; schema={schema}"
);
}
}