use indoc::indoc;
use std::collections::BTreeSet;
use color_eyre::eyre::{self, OptionExt as _};
use serde_json::{Value, json};
use test_util::prelude::sim_assert_eq;
#[path = "common/chart_instances.rs"]
mod chart_instances;
#[path = "common/schema_roundtrip.rs"]
mod schema_roundtrip;
#[path = "common/values_yaml.rs"]
mod values_yaml;
struct SemanticCase {
label: &'static str,
overrides: Value,
accepted: bool,
rejected_path: Option<&'static str>,
}
impl SemanticCase {
fn accepted(label: &'static str, overrides: Value) -> Self {
Self {
label,
overrides,
accepted: true,
rejected_path: None,
}
}
fn rejected(label: &'static str, rejected_path: &'static str, overrides: Value) -> Self {
Self {
label,
overrides,
accepted: false,
rejected_path: Some(rejected_path),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct ValidationFailure {
instance_path: String,
message: String,
}
fn assert_chart_cases(chart: &str, cases: Vec<SemanticCase>) -> eyre::Result<()> {
let schema = schema_roundtrip::generate_chart_schema_for_path(chart)?;
let validator = jsonschema::validator_for(&schema)
.map_err(|error| eyre::eyre!("compile {chart} schema: {error}"))?;
let validation_errors = |instance: &Value| {
validator
.iter_errors(instance)
.map(|error| ValidationFailure {
instance_path: error.instance_path().to_string(),
message: error.to_string(),
})
.collect::<BTreeSet<_>>()
};
let defaults = chart_instances::with_override(chart, json!({}))?;
let baseline_errors = validation_errors(&defaults);
let mut failures = Vec::new();
for case in cases {
let instance = chart_instances::with_override(chart, case.overrides)?;
let errors = validation_errors(&instance);
let additional_errors: Vec<_> = errors.difference(&baseline_errors).cloned().collect();
if case.accepted {
if !additional_errors.is_empty() {
failures.push(format!(
"{} should validate, got new errors: {additional_errors:#?}",
case.label
));
}
} else {
let rejected_path = case.rejected_path.ok_or_eyre("rejected case path")?;
let path_prefix = format!("{rejected_path}/");
if !additional_errors.iter().any(|error| {
if rejected_path.is_empty() {
return error.instance_path.is_empty();
}
if error.instance_path.is_empty() {
return false;
}
let error_prefix = format!("{}/", error.instance_path);
error.instance_path == rejected_path
|| error.instance_path.starts_with(&path_prefix)
|| rejected_path.starts_with(&error_prefix)
}) {
failures.push(format!(
"{} should be rejected at {rejected_path}; new errors were: {additional_errors:#?}",
case.label
));
}
}
}
if failures.is_empty() {
Ok(())
} else {
Err(eyre::eyre!("{chart}:\n{}", failures.join("\n")))
}
}
#[test]
fn fluent_bit_extra_containers_rejects_scalar_complement() -> eyre::Result<()> {
assert_chart_cases(
"fluent-bit",
vec![
SemanticCase::rejected(
"numeric extraContainers",
"/extraContainers",
json!({ "extraContainers": 7 }),
),
SemanticCase::accepted(
"templated extraContainers",
json!({ "extraContainers": "- name: audit\n image: busybox:1.36" }),
),
SemanticCase::accepted(
"structured extraContainers",
json!({ "extraContainers": [{ "name": "audit", "image": "busybox:1.36" }] }),
),
],
)
}
#[test]
fn fluent_bit_fixed_volume_lists_require_their_serialized_inputs() -> eyre::Result<()> {
assert_chart_cases(
"fluent-bit",
vec![
SemanticCase::rejected(
"the DaemonSet volume payload cannot be deleted",
"",
json!({ "daemonSetVolumes": null }),
),
SemanticCase::rejected(
"the DaemonSet volume-mount payload cannot be deleted",
"",
json!({ "daemonSetVolumeMounts": null }),
),
SemanticCase::accepted(
"a Deployment does not serialize the DaemonSet payloads",
json!({
"kind": "Deployment",
"daemonSetVolumes": null,
"daemonSetVolumeMounts": null,
}),
),
],
)
}
#[test]
fn minio_extra_containers_rejects_scalar_complement() -> eyre::Result<()> {
assert_chart_cases(
"minio",
vec![
SemanticCase::rejected(
"numeric extraContainers",
"/extraContainers",
json!({ "extraContainers": 7 }),
),
SemanticCase::accepted(
"templated extraContainers",
json!({ "extraContainers": "- name: audit\n image: busybox:1.36" }),
),
SemanticCase::accepted(
"structured extraContainers",
json!({ "extraContainers": [{ "name": "audit", "image": "busybox:1.36" }] }),
),
],
)
}
#[test]
fn oauth2_proxy_consumers_follow_the_selected_helper_branch() -> eyre::Result<()> {
assert_chart_cases(
"oauth2-proxy",
vec![
SemanticCase::rejected(
"map kubeVersion",
"/kubeVersion",
json!({ "kubeVersion": { "major": 1 } }),
),
SemanticCase::accepted("string kubeVersion", json!({ "kubeVersion": "1.29.0" })),
SemanticCase::accepted(
"ignored map configFile in generated alpha mode",
json!({
"alphaConfig": { "enabled": true },
"config": {
"forceLegacyConfig": false,
"configFile": { "ignored": true }
}
}),
),
SemanticCase::rejected(
"map configFile in live inline-custom mode",
"/config/configFile",
json!({
"alphaConfig": { "enabled": true },
"config": {
"forceLegacyConfig": true,
"configFile": { "invalid": true }
}
}),
),
SemanticCase::accepted(
"string configFile in live inline-custom mode",
json!({
"alphaConfig": { "enabled": true },
"config": {
"forceLegacyConfig": true,
"configFile": "email_domains = [\"*\"]"
}
}),
),
],
)
}
#[test]
fn oauth2_proxy_service_port_keeps_the_unconditional_numeric_contract() -> eyre::Result<()> {
assert_chart_cases(
"oauth2-proxy",
vec![
SemanticCase::accepted(
"integer service port",
json!({ "service": { "portNumber": 8080 } }),
),
SemanticCase::accepted(
"numeric-string service port",
json!({ "service": { "portNumber": "8080" } }),
),
SemanticCase::rejected(
"named service port",
"/service/portNumber",
json!({ "service": { "portNumber": "http" } }),
),
],
)
}
#[test]
fn bitnami_postgresql_fips_contracts_follow_their_live_branches() -> eyre::Result<()> {
assert_chart_cases(
"bitnami-postgresql",
vec![
SemanticCase::accepted(
"global false FIPS sentinel",
json!({ "global": { "defaultFips": false } }),
),
SemanticCase::rejected(
"global true FIPS value",
"/global/defaultFips",
json!({ "global": { "defaultFips": true } }),
),
SemanticCase::accepted(
"dormant password-update OpenSSL FIPS value",
json!({
"passwordUpdateJob": {
"enabled": false,
"fips": { "openssl": true }
}
}),
),
SemanticCase::accepted(
"dormant backup FIPS value",
json!({
"backup": {
"enabled": false,
"cronjob": { "fips": "audit" }
}
}),
),
SemanticCase::rejected(
"live backup FIPS value",
"/backup/cronjob/fips",
json!({
"backup": {
"enabled": true,
"cronjob": { "fips": "audit" }
}
}),
),
],
)
}
#[test]
fn istiod_string_and_object_consumers_reject_wrong_kinds() -> eyre::Result<()> {
assert_chart_cases(
"istiod",
vec![
SemanticCase::rejected(
"map remotePilotAddress",
"/global/remotePilotAddress",
json!({
"global": { "remotePilotAddress": { "host": "1.2.3.4" } },
"istiodRemote": { "enabled": true }
}),
),
SemanticCase::accepted(
"string remotePilotAddress",
json!({
"global": { "remotePilotAddress": "1.2.3.4" },
"istiodRemote": { "enabled": true }
}),
),
SemanticCase::rejected(
"scalar gateways passed to pick",
"/gateways",
json!({ "gateways": 7 }),
),
SemanticCase::accepted("default object gateways", json!({})),
],
)
}
#[test]
fn istiod_constructed_tpl_selector_reaches_removed_value_fail() -> eyre::Result<()> {
assert_chart_cases(
"istiod",
vec![
SemanticCase::rejected(
"truthy removed Stackdriver option",
"/telemetry/v2/stackdriver",
json!({
"telemetry": {
"v2": { "stackdriver": { "disableOutbound": true } }
}
}),
),
SemanticCase::accepted(
"empty removed Stackdriver option",
json!({
"telemetry": {
"v2": { "stackdriver": { "disableOutbound": "" } }
}
}),
),
],
)
}
#[test]
fn vault_fullname_override_requires_a_string_when_truthy() -> eyre::Result<()> {
assert_chart_cases(
"vault",
vec![
SemanticCase::rejected(
"truthy map fullnameOverride",
"/fullnameOverride",
json!({ "fullnameOverride": { "bad": true } }),
),
SemanticCase::rejected(
"truthy list fullnameOverride",
"/fullnameOverride",
json!({ "fullnameOverride": ["bad"] }),
),
SemanticCase::accepted(
"empty map fullnameOverride",
json!({ "fullnameOverride": {} }),
),
SemanticCase::accepted(
"empty list fullnameOverride",
json!({ "fullnameOverride": [] }),
),
SemanticCase::rejected(
"numeric fullnameOverride",
"/fullnameOverride",
json!({ "fullnameOverride": 7 }),
),
SemanticCase::rejected(
"boolean fullnameOverride",
"/fullnameOverride",
json!({ "fullnameOverride": true }),
),
SemanticCase::accepted(
"string fullnameOverride",
json!({ "fullnameOverride": "vault-audit" }),
),
],
)
}
#[test]
fn promtail_string_consumers_and_range_keys_keep_their_domains() -> eyre::Result<()> {
assert_chart_cases(
"promtail",
vec![
SemanticCase::rejected(
"numeric image tag",
"/image/tag",
json!({ "image": { "tag": 7 } }),
),
SemanticCase::rejected(
"boolean image tag",
"/image/tag",
json!({ "image": { "tag": true } }),
),
SemanticCase::accepted("string image tag", json!({ "image": { "tag": "3.0.0" } })),
SemanticCase::rejected(
"array extraPorts with integer range keys",
"/extraPorts",
json!({
"extraPorts": [{
"containerPort": 1514,
"service": { "port": 1514 }
}]
}),
),
SemanticCase::accepted(
"map extraPorts with string range keys",
json!({
"extraPorts": {
"syslog": {
"containerPort": 1514,
"service": { "port": 1514 }
}
}
}),
),
],
)
}
#[test]
fn nfs_archive_on_delete_keeps_quoted_scalar_forms() -> eyre::Result<()> {
assert_chart_cases(
"nfs-subdir-external-provisioner",
vec![
SemanticCase::accepted(
"string archiveOnDelete",
json!({ "storageClass": { "archiveOnDelete": "false" } }),
),
SemanticCase::accepted(
"boolean archiveOnDelete",
json!({ "storageClass": { "archiveOnDelete": false } }),
),
],
)
}
#[test]
fn nfs_tolerations_rejects_a_scalar_on_the_configured_kubernetes_version() -> eyre::Result<()> {
assert_chart_cases(
"nfs-subdir-external-provisioner",
vec![
SemanticCase::rejected(
"truthy string tolerations",
"/tolerations",
json!({ "tolerations": "audit" }),
),
SemanticCase::accepted("false tolerations", json!({ "tolerations": false })),
SemanticCase::accepted("zero tolerations", json!({ "tolerations": 0 })),
SemanticCase::accepted("empty-string tolerations", json!({ "tolerations": "" })),
SemanticCase::accepted("null tolerations", json!({ "tolerations": null })),
SemanticCase::accepted(
"structured tolerations",
json!({
"tolerations": [{
"key": "dedicated",
"operator": "Exists",
"effect": "NoSchedule"
}]
}),
),
],
)
}
#[test]
fn postgres_operator_extra_envs_requires_a_sequence() -> eyre::Result<()> {
assert_chart_cases(
"zalando-postgres-operator",
vec![
SemanticCase::rejected(
"string extraEnvs",
"/extraEnvs",
json!({ "extraEnvs": "audit" }),
),
SemanticCase::accepted(
"EnvVar list",
json!({ "extraEnvs": [{ "name": "AUDIT", "value": "true" }] }),
),
],
)
}
#[test]
fn postgres_operator_ui_extra_envs_requires_a_sequence() -> eyre::Result<()> {
assert_chart_cases(
"zalando-postgres-operator-ui",
vec![
SemanticCase::rejected(
"string extraEnvs",
"/extraEnvs",
json!({ "extraEnvs": "audit" }),
),
SemanticCase::accepted(
"EnvVar list",
json!({ "extraEnvs": [{ "name": "AUDIT", "value": "true" }] }),
),
],
)
}
#[test]
fn external_dns_affinity_preserves_the_with_skip_domain() -> eyre::Result<()> {
assert_chart_cases(
"external-dns",
vec![
SemanticCase::accepted(
"false affinity skipped by with",
json!({ "affinity": false }),
),
SemanticCase::accepted("zero affinity skipped by with", json!({ "affinity": 0 })),
SemanticCase::accepted("empty affinity skipped by with", json!({ "affinity": "" })),
SemanticCase::rejected(
"truthy string affinity",
"/affinity",
json!({ "affinity": "audit" }),
),
SemanticCase::accepted(
"object affinity",
json!({ "affinity": { "podAntiAffinity": {} } }),
),
],
)
}
#[test]
fn external_dns_helper_return_partitions_the_webhook_provider_contract() -> eyre::Result<()> {
let provider = |name: &str, pull_policy: Value| {
json!({
"provider": {
"name": name,
"webhook": {
"image": {
"repository": "example/webhook",
"tag": "1.0",
"pullPolicy": pull_policy
}
}
}
})
};
assert_chart_cases(
"external-dns",
vec![
SemanticCase::rejected(
"active webhook with a non-string image pull policy",
"/provider/webhook/image/pullPolicy",
provider("webhook", json!(7)),
),
SemanticCase::accepted("dormant webhook image policy", provider("aws", json!(7))),
SemanticCase::accepted(
"active webhook with a string image pull policy",
provider("webhook", json!("IfNotPresent")),
),
],
)
}
#[test]
fn grafana_nested_ranges_and_has_key_require_object_members() -> eyre::Result<()> {
let dashboards = json!({ "default": { "audit": { "json": "{}" } } });
assert_chart_cases(
"grafana",
vec![
SemanticCase::rejected(
"false dashboard provider member",
"/dashboardProviders/dashboardproviders.yaml",
json!({
"dashboards": dashboards.clone(),
"dashboardProviders": { "dashboardproviders.yaml": false }
}),
),
SemanticCase::rejected(
"zero dashboard provider member",
"/dashboardProviders/dashboardproviders.yaml",
json!({
"dashboards": dashboards.clone(),
"dashboardProviders": { "dashboardproviders.yaml": 0 }
}),
),
SemanticCase::rejected(
"empty dashboard provider member",
"/dashboardProviders/dashboardproviders.yaml",
json!({
"dashboards": dashboards.clone(),
"dashboardProviders": { "dashboardproviders.yaml": "" }
}),
),
SemanticCase::rejected(
"truthy dashboard provider member",
"/dashboardProviders/dashboardproviders.yaml",
json!({
"dashboards": dashboards.clone(),
"dashboardProviders": { "dashboardproviders.yaml": "audit" }
}),
),
SemanticCase::rejected(
"list dashboard provider member",
"/dashboardProviders/dashboardproviders.yaml",
json!({
"dashboards": dashboards.clone(),
"dashboardProviders": { "dashboardproviders.yaml": [] }
}),
),
SemanticCase::accepted(
"object dashboard provider member",
json!({
"dashboards": dashboards.clone(),
"dashboardProviders": {
"dashboardproviders.yaml": {
"apiVersion": 1,
"providers": []
}
}
}),
),
SemanticCase::rejected(
"scalar dashboard passed to hasKey",
"/dashboards/default/audit",
json!({ "dashboards": { "default": { "audit": 7 } } }),
),
SemanticCase::accepted(
"object dashboard passed to hasKey",
json!({ "dashboards": dashboards }),
),
],
)
}
#[test]
fn grafana_leaked_secret_walk_requires_map_hosts() -> eyre::Result<()> {
assert_chart_cases(
"grafana",
vec![
SemanticCase::rejected(
"integer sensitive host",
"/grafana.ini/database",
json!({ "grafana.ini": { "database": 7 } }),
),
SemanticCase::rejected(
"string sensitive host",
"/grafana.ini/database",
json!({ "grafana.ini": { "database": "text" } }),
),
SemanticCase::rejected(
"list sensitive host",
"/grafana.ini/database",
json!({ "grafana.ini": { "database": [] } }),
),
SemanticCase::rejected(
"atomic dotted-key sensitive host",
"/grafana.ini/auth.basic",
json!({ "grafana.ini": { "auth.basic": 7 } }),
),
SemanticCase::accepted(
"empty sensitive host",
json!({ "grafana.ini": { "database": {} } }),
),
SemanticCase::accepted(
"variable-expanded sensitive leaf",
json!({ "grafana.ini": { "database": { "password": "${DB_PASS}" } } }),
),
SemanticCase::accepted(
"dormant assertion keeps scalar hosts open",
json!({
"assertNoLeakedSecrets": false,
"grafana.ini": { "database": 7 }
}),
),
],
)
}
#[test]
fn grafana_dotted_config_root_hosts_grouped_get_selectors() -> eyre::Result<()> {
assert_chart_cases(
"grafana",
vec![
SemanticCase::rejected(
"deleted dotted config root",
"",
json!({ "grafana.ini": null }),
),
SemanticCase::rejected(
"scalar dotted config root",
"/grafana.ini",
json!({ "grafana.ini": "audit" }),
),
SemanticCase::accepted("empty dotted config root", json!({ "grafana.ini": {} })),
],
)
}
#[test]
fn sealed_secrets_liveness_probe_keeps_its_object_type() -> eyre::Result<()> {
assert_chart_cases(
"sealed-secrets",
vec![
SemanticCase::rejected(
"string livenessProbe",
"/livenessProbe",
json!({ "livenessProbe": "audit" }),
),
SemanticCase::accepted(
"object livenessProbe",
json!({
"livenessProbe": {
"enabled": true,
"initialDelaySeconds": 0,
"periodSeconds": 10
}
}),
),
],
)
}
#[test]
fn jaeger_range_body_requires_string_arguments() -> eyre::Result<()> {
assert_chart_cases(
"jaeger",
vec![
SemanticCase::rejected(
"integer jaeger.args",
"/jaeger/args",
json!({ "jaeger": { "args": 7 } }),
),
SemanticCase::accepted(
"string argument list",
json!({ "jaeger": { "args": ["--query.base-path=/jaeger"] } }),
),
],
)
}
#[test]
fn jenkins_range_body_requires_string_plugins() -> eyre::Result<()> {
assert_chart_cases(
"jenkins",
vec![
SemanticCase::rejected(
"integer controller.installPlugins",
"/controller/installPlugins",
json!({ "controller": { "installPlugins": 7 } }),
),
SemanticCase::accepted(
"string plugin list",
json!({ "controller": { "installPlugins": ["git:5.10.1"] } }),
),
],
)
}
#[test]
fn jenkins_dormant_printf_fallback_allows_a_deleted_operand() -> eyre::Result<()> {
assert_chart_cases(
"jenkins",
vec![SemanticCase::accepted(
"deleted jenkinsRef with the sidecar folder default",
json!({ "controller": { "jenkinsRef": null } }),
)],
)
}
#[test]
fn nats_selected_printf_operands_require_an_image_spelling() -> eyre::Result<()> {
assert_chart_cases(
"nats",
vec![SemanticCase::rejected(
"all image spellings deleted",
"",
json!({
"container": {
"image": {
"repository": null,
"tag": null,
"pullPolicy": null,
"registry": null,
"digest": null,
"fullImageName": null,
}
}
}),
)],
)
}
#[test]
fn velero_range_values_and_merge_operands_keep_their_domains() -> eyre::Result<()> {
assert_chart_cases(
"velero",
vec![
SemanticCase::rejected(
"numeric credentials extraEnvVars member",
"/credentials/extraEnvVars/TOKEN",
json!({ "credentials": { "extraEnvVars": { "TOKEN": 7 } } }),
),
SemanticCase::accepted(
"string credentials extraEnvVars member",
json!({ "credentials": { "extraEnvVars": { "TOKEN": "secret" } } }),
),
SemanticCase::rejected(
"string podSecurityContext passed to merge",
"/podSecurityContext",
json!({ "podSecurityContext": "audit" }),
),
SemanticCase::accepted(
"object podSecurityContext",
json!({ "podSecurityContext": { "runAsNonRoot": true } }),
),
],
)
}
#[test]
fn velero_merge_shadowing_scopes_legacy_security_context() -> eyre::Result<()> {
assert_chart_cases(
"velero",
vec![
SemanticCase::rejected(
"active legacy runAsUser reaches the rendered pod context",
"/securityContext/runAsUser",
json!({ "securityContext": { "runAsUser": { "bad": true } } }),
),
SemanticCase::accepted(
"shadowed legacy runAsUser never renders",
json!({
"podSecurityContext": { "runAsUser": 1000 },
"securityContext": { "runAsUser": { "bad": true } }
}),
),
SemanticCase::accepted(
"integer legacy runAsUser renders valid",
json!({ "securityContext": { "runAsUser": 1000 } }),
),
],
)
}
#[test]
fn surveyor_range_members_and_intermediate_secrets_are_structural() -> eyre::Result<()> {
assert_chart_cases(
"surveyor",
vec![
SemanticCase::rejected(
"scalar account array member",
"/config/jetstream/accounts",
json!({ "config": { "jetstream": { "enabled": true, "accounts": [7] } } }),
),
SemanticCase::rejected(
"scalar account map member",
"/config/jetstream/accounts",
json!({ "config": { "jetstream": { "enabled": true, "accounts": { "A": 7 } } } }),
),
SemanticCase::accepted(
"object account array member",
json!({
"config": {
"jetstream": { "enabled": true, "accounts": [{ "name": "A" }] }
}
}),
),
SemanticCase::accepted(
"object account map member",
json!({
"config": {
"jetstream": {
"enabled": true,
"accounts": { "A": { "name": "A" } }
}
}
}),
),
SemanticCase::rejected(
"credentials without intermediate secret",
"/config",
json!({ "config": { "credentials": { "audit": 1 } } }),
),
SemanticCase::accepted(
"credentials with intermediate secret",
json!({
"config": {
"credentials": { "secret": { "name": "nats-creds", "key": "sys.creds" } }
}
}),
),
SemanticCase::rejected(
"password without intermediate secret",
"/config",
json!({ "config": { "password": { "audit": 1 } } }),
),
SemanticCase::accepted(
"password with intermediate secret",
json!({
"config": {
"password": { "secret": { "name": "nats-auth", "key": "password" } }
}
}),
),
],
)
}
#[test]
fn harbor_string_comparisons_reject_composite_operands() -> eyre::Result<()> {
assert_chart_cases(
"harbor",
vec![
SemanticCase::rejected(
"map logLevel",
"/logLevel",
json!({ "logLevel": { "level": "info" } }),
),
SemanticCase::accepted("string logLevel", json!({ "logLevel": "info" })),
SemanticCase::rejected(
"string internal TLS selector",
"/internalTLS/enabled",
json!({ "internalTLS": { "enabled": "true" } }),
),
SemanticCase::accepted(
"Boolean internal TLS selector",
json!({ "internalTLS": { "enabled": true } }),
),
],
)
}
#[test]
fn signoz_comparisons_and_guarded_ranges_keep_exact_scope() -> eyre::Result<()> {
let config = json!({
"route": { "receiver": "audit" },
"receivers": [{ "name": "audit" }]
});
assert_chart_cases(
"signoz-signoz",
vec![
SemanticCase::rejected(
"map global storageClass",
"/global/storageClass",
json!({ "global": { "storageClass": { "name": "standard" } } }),
),
SemanticCase::rejected(
"list global storageClass",
"/global/storageClass",
json!({ "global": { "storageClass": ["standard"] } }),
),
SemanticCase::rejected(
"numeric global storageClass",
"/global/storageClass",
json!({ "global": { "storageClass": 7 } }),
),
SemanticCase::accepted(
"string global storageClass",
json!({ "global": { "storageClass": "standard" } }),
),
SemanticCase::accepted(
"string templates under disabled alertmanager",
json!({
"alertmanager": {
"enabled": false,
"config": config.clone(),
"templates": "audit"
}
}),
),
SemanticCase::rejected(
"string templates under live alertmanager range",
"/alertmanager/templates",
json!({
"alertmanager": {
"enabled": true,
"config": config.clone(),
"templates": "audit"
}
}),
),
SemanticCase::accepted(
"map templates under live alertmanager range",
json!({
"alertmanager": {
"enabled": true,
"config": config,
"templates": { "audit.tmpl": "audit" }
}
}),
),
],
)
}
#[test]
fn kube_state_metrics_collection_consumers_keep_documented_unions() -> eyre::Result<()> {
assert_chart_cases(
"kube-state-metrics",
vec![
SemanticCase::rejected(
"integer collectors",
"/collectors",
json!({ "collectors": 7 }),
),
SemanticCase::accepted("collector list", json!({ "collectors": ["pods"] })),
SemanticCase::accepted(
"namespace list",
json!({ "namespaces": ["default", "kube-system"] }),
),
SemanticCase::accepted(
"comma-separated namespaces",
json!({ "namespaces": "default,kube-system" }),
),
],
)
}
#[test]
fn kube_prometheus_stack_must_uniq_requires_a_list() -> eyre::Result<()> {
assert_chart_cases(
"kube-prometheus-stack",
vec![
SemanticCase::rejected(
"scalar denyNamespaces",
"/prometheusOperator/denyNamespaces",
json!({ "prometheusOperator": { "denyNamespaces": 7 } }),
),
SemanticCase::accepted(
"denyNamespaces list",
json!({ "prometheusOperator": { "denyNamespaces": ["kube-system"] } }),
),
],
)
}
#[test]
fn argo_cd_concat_rejects_all_non_list_operands() -> eyre::Result<()> {
assert_chart_cases(
"argo-cd",
vec![
SemanticCase::rejected(
"false global.env",
"/global/env",
json!({ "global": { "env": false } }),
),
SemanticCase::rejected(
"zero global.env",
"/global/env",
json!({ "global": { "env": 0 } }),
),
SemanticCase::rejected(
"empty global.env",
"/global/env",
json!({ "global": { "env": "" } }),
),
SemanticCase::rejected(
"false controller.env",
"/controller/env",
json!({ "controller": { "env": false } }),
),
SemanticCase::accepted(
"EnvVar arrays",
json!({
"global": { "env": [{ "name": "GLOBAL_AUDIT", "value": "true" }] },
"controller": { "env": [{ "name": "AUDIT", "value": "true" }] }
}),
),
],
)
}
#[test]
fn cloudnative_pg_merge_rejects_falsy_wrong_kinds_when_live() -> eyre::Result<()> {
assert_chart_cases(
"cloudnative-pg",
vec![
SemanticCase::rejected(
"false config.data under namespaced merge",
"/config/data",
json!({ "config": { "clusterWide": false, "data": false } }),
),
SemanticCase::rejected(
"zero config.data under namespaced merge",
"/config/data",
json!({ "config": { "clusterWide": false, "data": 0 } }),
),
SemanticCase::rejected(
"empty config.data under namespaced merge",
"/config/data",
json!({ "config": { "clusterWide": false, "data": "" } }),
),
SemanticCase::accepted(
"object config.data under namespaced merge",
json!({
"config": {
"clusterWide": false,
"data": { "INHERITED_LABELS": "environment" }
}
}),
),
SemanticCase::accepted(
"object config.data under cluster-wide serialization",
json!({
"config": {
"clusterWide": true,
"data": { "INHERITED_LABELS": "environment" }
}
}),
),
],
)
}
#[test]
fn airflow_webserver_contract_binds_under_version_guard() -> eyre::Result<()> {
assert_chart_cases(
"airflow",
vec![
SemanticCase::accepted(
"map base_url with Airflow 3 webserver branch disabled",
json!({
"airflowVersion": "3.2.2",
"config": { "webserver": { "base_url": { "host": "airflow" } } }
}),
),
SemanticCase::accepted(
"URL base_url with Airflow 2 webserver branch live",
json!({
"airflowVersion": "2.11.0",
"config": { "webserver": { "base_url": "https://airflow.example.com" } }
}),
),
SemanticCase::rejected(
"map base_url with Airflow 2 webserver branch live",
"/config/webserver/base_url",
json!({
"airflowVersion": "2.11.0",
"config": { "webserver": { "base_url": { "host": "airflow" } } }
}),
),
],
)
}
#[test]
fn airflow_version_requires_semver_lexical_form() -> eyre::Result<()> {
assert_chart_cases(
"airflow",
vec![
SemanticCase::rejected(
"invalid airflowVersion",
"/airflowVersion",
json!({ "airflowVersion": "garbage" }),
),
SemanticCase::accepted("valid airflowVersion", json!({ "airflowVersion": "3.2.2" })),
],
)
}
#[test]
fn airflow_security_context_priority_keeps_dormant_fallbacks_open() -> eyre::Result<()> {
assert_chart_cases(
"airflow",
vec![
SemanticCase::accepted(
"deprecated scalar context is dormant behind the preferred pod context",
json!({
"workers": {
"securityContexts": { "pod": { "runAsUser": 50000 } },
"securityContext": 7
}
}),
),
SemanticCase::accepted(
"deprecated object context remains a valid live fallback",
json!({
"workers": {
"securityContexts": { "pod": {} },
"securityContext": { "runAsUser": 50000 }
}
}),
),
],
)
}
#[test]
fn postgres_operator_ui_team_elements_are_formatted_as_text() -> eyre::Result<()> {
assert_chart_cases(
"zalando-postgres-operator-ui",
vec![
SemanticCase::accepted("numeric teams", json!({ "envs": { "teams": [7, 8] } })),
SemanticCase::accepted(
"structured teams",
json!({ "envs": { "teams": [{ "name": "acid" }] } }),
),
SemanticCase::accepted(
"ordinary string teams",
json!({ "envs": { "teams": ["acid"] } }),
),
],
)
}
#[test]
fn kyverno_image_pull_secret_keys_require_a_map() -> eyre::Result<()> {
assert_chart_cases(
"kyverno",
vec![
SemanticCase::rejected(
"array passed to keys",
"/imagePullSecrets",
json!({ "imagePullSecrets": [{ "name": "regcred" }] }),
),
SemanticCase::accepted(
"map passed to keys",
json!({
"imagePullSecrets": {
"regcred": {
"registry": "registry.example.com",
"username": "audit",
"password": "secret"
}
}
}),
),
],
)
}
#[test]
fn bitnami_redis_ternary_selector_requires_boolean() -> eyre::Result<()> {
assert_chart_cases(
"bitnami-redis",
vec![
SemanticCase::rejected(
"string auth enabled",
"/auth/enabled",
json!({ "auth": { "enabled": "true" } }),
),
SemanticCase::accepted(
"Boolean auth enabled",
json!({ "auth": { "enabled": true } }),
),
],
)
}
#[test]
fn bitnami_redis_helper_preimages_follow_the_rendered_yaml_kinds() -> eyre::Result<()> {
assert_chart_cases(
"bitnami-redis",
vec![
SemanticCase::accepted(
"an empty common-annotation array leaves no merged members",
json!({ "commonAnnotations": [] }),
),
SemanticCase::accepted(
"false common annotations leave no merged members",
json!({ "commonAnnotations": false }),
),
SemanticCase::accepted(
"zero common annotations leave no merged members",
json!({ "commonAnnotations": 0 }),
),
SemanticCase::accepted(
"a falsy layer remains harmless beside a live merge sibling",
json!({
"commonAnnotations": [],
"serviceAccount": { "annotations": { "owner": "audit" } },
}),
),
SemanticCase::rejected(
"a numeric common-annotation member reaches ObjectMeta",
"/commonAnnotations/audit",
json!({ "commonAnnotations": { "audit": 7 } }),
),
SemanticCase::accepted(
"a safely formatted map becomes a scalar ServiceAccount name",
json!({ "master": { "serviceAccount": { "name": { "audit": 1 } } } }),
),
SemanticCase::rejected(
"a numeric ServiceAccount name reparses as a number",
"/master/serviceAccount/name",
json!({ "master": { "serviceAccount": { "name": 7 } } }),
),
SemanticCase::rejected(
"a list ServiceAccount name reparses as a sequence",
"/master/serviceAccount/name",
json!({ "master": { "serviceAccount": { "name": ["audit"] } } }),
),
],
)
}
#[test]
fn bitnami_redis_master_services_keep_provider_contracts() -> eyre::Result<()> {
assert_chart_cases(
"bitnami-redis",
vec![
SemanticCase::rejected(
"a malformed session-affinity config reaches the Service",
"/master/service/sessionAffinityConfig",
json!({ "master": { "service": {
"sessionAffinity": "ClientIP",
"sessionAffinityConfig": { "bogus": 7 },
} } }),
),
SemanticCase::rejected(
"Service external IP members are strings",
"/master/service/externalIPs/0",
json!({ "master": { "service": { "externalIPs": [7] } } }),
),
SemanticCase::rejected(
"the master Service port is an integer",
"/master/service/ports/redis",
json!({ "master": { "service": { "ports": { "redis": "oops" } } } }),
),
SemanticCase::accepted(
"valid master Service settings render",
json!({ "master": { "service": {
"ports": { "redis": 6379 },
"externalIPs": ["10.10.10.1"],
"sessionAffinity": "ClientIP",
"sessionAffinityConfig": { "clientIP": { "timeoutSeconds": 300 } },
} } }),
),
SemanticCase::accepted(
"a zero master count keeps malformed Service payloads dormant",
json!({ "master": {
"count": 0,
"service": {
"ports": { "redis": "oops" },
"externalIPs": [7],
"sessionAffinity": "ClientIP",
"sessionAffinityConfig": { "bogus": 7 },
},
} }),
),
SemanticCase::accepted(
"Sentinel selects its own headless port",
json!({
"sentinel": { "enabled": true },
"master": { "service": { "ports": { "redis": "oops" } } },
}),
),
SemanticCase::accepted(
"the labels merge discards a non-mapping layer",
json!({ "master": { "podLabels": 7 } }),
),
SemanticCase::accepted(
"the annotations merge discards a non-mapping layer",
json!({ "master": { "service": { "annotations": [] } } }),
),
],
)
}
#[test]
fn direct_provider_required_leaves_survive_default_deletion() -> eyre::Result<()> {
assert_chart_cases(
"sealed-secrets",
vec![
SemanticCase::rejected(
"the controller Service port cannot be deleted",
"/service",
json!({ "service": { "port": null } }),
),
SemanticCase::rejected(
"the metrics Service port cannot be deleted",
"/metrics/service",
json!({ "metrics": { "service": { "port": null } } }),
),
SemanticCase::rejected(
"the ClusterRoleBinding role name cannot be deleted",
"/rbac",
json!({ "rbac": { "clusterRoleName": null } }),
),
SemanticCase::accepted(
"valid required Service and RBAC leaves render",
json!({
"service": { "port": 8080 },
"metrics": { "service": { "port": 8081 } },
"rbac": { "clusterRoleName": "secrets-unsealer" },
}),
),
],
)?;
assert_chart_cases(
"surveyor",
vec![
SemanticCase::rejected(
"the Service port cannot be deleted",
"/service",
json!({ "service": { "port": null } }),
),
SemanticCase::accepted(
"a valid Service port renders",
json!({ "service": { "port": 7777 } }),
),
],
)?;
assert_chart_cases(
"trivy-operator",
vec![
SemanticCase::rejected(
"the metrics Service port cannot be deleted",
"/service",
json!({ "service": { "metricsPort": null } }),
),
SemanticCase::accepted(
"a valid metrics Service port renders",
json!({ "service": { "metricsPort": 80 } }),
),
],
)?;
assert_chart_cases(
"velero",
vec![
SemanticCase::rejected(
"the administrator ClusterRole name cannot be deleted",
"/rbac",
json!({ "rbac": { "clusterAdministratorName": null } }),
),
SemanticCase::accepted(
"a valid administrator ClusterRole name renders",
json!({ "rbac": { "clusterAdministratorName": "cluster-admin" } }),
),
],
)?;
assert_chart_cases(
"zalando-postgres-operator",
vec![
SemanticCase::rejected(
"the REST API probe port cannot be deleted",
"/configLoggingRestApi",
json!({ "configLoggingRestApi": { "api_port": null } }),
),
SemanticCase::accepted(
"a valid REST API probe port renders",
json!({ "configLoggingRestApi": { "api_port": 8080 } }),
),
],
)
}
#[test]
fn direct_provider_required_storage_and_service_leaves_survive_deletion() -> eyre::Result<()> {
assert_chart_cases(
"traefik",
vec![
SemanticCase::rejected(
"an enabled persistent volume requires its name",
"/persistence",
json!({ "persistence": { "enabled": true, "name": null } }),
),
SemanticCase::rejected(
"an enabled persistent volume requires its mount path",
"/persistence",
json!({ "persistence": { "enabled": true, "path": null } }),
),
SemanticCase::rejected(
"the metrics entrypoint requires its port",
"/ports/metrics",
json!({
"metrics": { "prometheus": { "service": { "enabled": true } } },
"ports": { "metrics": { "port": null } },
}),
),
SemanticCase::accepted(
"valid persistent-volume and metrics-port leaves render",
json!({
"persistence": { "enabled": true, "name": "data", "path": "/data" },
"metrics": { "prometheus": { "service": { "enabled": true } } },
"ports": { "metrics": { "port": 9100 } },
}),
),
],
)?;
assert_chart_cases(
"vault",
vec![
SemanticCase::rejected(
"the injector container port cannot be deleted",
"/injector",
json!({ "injector": { "port": null } }),
),
SemanticCase::rejected(
"enabled data storage requires its mount path",
"/server/dataStorage",
json!({ "server": { "dataStorage": { "mountPath": null } } }),
),
SemanticCase::rejected(
"enabled data storage requires its requested size",
"/server/dataStorage",
json!({ "server": { "dataStorage": { "size": null } } }),
),
SemanticCase::rejected(
"enabled audit storage requires its mount path",
"/server/auditStorage",
json!({ "server": { "auditStorage": { "enabled": true, "mountPath": null } } }),
),
SemanticCase::rejected(
"enabled audit storage requires its requested size",
"/server/auditStorage",
json!({ "server": { "auditStorage": { "enabled": true, "size": null } } }),
),
SemanticCase::rejected(
"the server Service requires its target port",
"/server/service",
json!({ "server": { "service": { "targetPort": null } } }),
),
SemanticCase::accepted(
"valid injector, storage, and Service leaves render",
json!({
"injector": { "port": 8080 },
"server": {
"dataStorage": { "mountPath": "/vault/data", "size": "10Gi" },
"auditStorage": {
"enabled": true,
"mountPath": "/vault/audit",
"size": "10Gi",
},
"service": { "targetPort": 8200 },
},
}),
),
],
)
}
#[test]
fn signoz_direct_provider_required_leaves_survive_deletion() -> eyre::Result<()> {
assert_chart_cases(
"signoz-signoz",
vec![
SemanticCase::rejected(
"the SigNoz liveness probe requires its port",
"/signoz/livenessProbe",
json!({ "signoz": { "livenessProbe": { "port": null } } }),
),
SemanticCase::rejected(
"the collector readiness probe requires its port",
"/otelCollector/readinessProbe",
json!({ "otelCollector": { "readinessProbe": { "port": null } } }),
),
SemanticCase::rejected(
"enabled SigNoz persistence requires its size",
"/signoz/persistence",
json!({ "signoz": { "persistence": { "size": null } } }),
),
SemanticCase::rejected(
"enabled Alertmanager persistence requires its size",
"/alertmanager/persistence",
json!({
"alertmanager": {
"enabled": true,
"persistence": { "size": null },
},
}),
),
SemanticCase::rejected(
"the SigNoz Service requires its public port",
"/signoz/service",
json!({ "signoz": { "service": { "port": null } } }),
),
SemanticCase::rejected(
"the Alertmanager Service requires its port",
"/alertmanager/service",
json!({
"alertmanager": {
"enabled": true,
"service": { "port": null },
},
}),
),
SemanticCase::rejected(
"an enabled collector port requires its container port",
"/otelCollector/ports/otlp",
json!({ "otelCollector": { "ports": { "otlp": { "containerPort": null } } } }),
),
SemanticCase::rejected(
"an enabled collector port requires its Service port",
"/otelCollector/ports/otlp",
json!({ "otelCollector": { "ports": { "otlp": { "servicePort": null } } } }),
),
SemanticCase::accepted(
"valid probe, persistence, and Service leaves render",
json!({
"signoz": {
"livenessProbe": { "port": "http" },
"persistence": { "size": "1Gi" },
"service": { "port": 8080 },
},
"alertmanager": {
"enabled": true,
"persistence": { "size": "100Mi" },
"service": { "port": 9093 },
},
"otelCollector": {
"readinessProbe": { "port": 13133 },
"ports": {
"otlp": { "containerPort": 4317, "servicePort": 4317 },
},
},
}),
),
],
)
}
#[test]
fn vault_helper_splices_keep_metadata_member_types() -> eyre::Result<()> {
assert_chart_cases(
"vault",
vec![
SemanticCase::rejected(
"injector pod annotation values are strings",
"/injector/annotations/audit",
json!({ "injector": { "annotations": { "audit": 7 } } }),
),
SemanticCase::rejected(
"webhook annotation values are strings",
"/injector/webhook/annotations/audit",
json!({ "injector": { "webhook": { "annotations": { "audit": 7 } } } }),
),
SemanticCase::rejected(
"injector ServiceAccount annotation values are strings",
"/injector/serviceAccount/annotations/audit",
json!({ "injector": { "serviceAccount": { "annotations": { "audit": 7 } } } }),
),
SemanticCase::rejected(
"server ServiceAccount annotation values are strings",
"/server/serviceAccount/annotations/audit",
json!({ "server": { "serviceAccount": { "annotations": { "audit": 7 } } } }),
),
SemanticCase::rejected(
"data-volume annotation values are strings",
"/server/dataStorage/annotations/audit",
json!({ "server": { "dataStorage": { "annotations": { "audit": 7 } } } }),
),
SemanticCase::rejected(
"data-volume label values are strings",
"/server/dataStorage/labels/audit",
json!({ "server": { "dataStorage": { "labels": { "audit": 7 } } } }),
),
SemanticCase::accepted(
"string metadata values survive every helper splice",
json!({
"injector": {
"annotations": { "audit": "ok" },
"webhook": { "annotations": { "audit": "ok" } },
"serviceAccount": { "annotations": { "audit": "ok" } },
},
"server": {
"serviceAccount": { "annotations": { "audit": "ok" } },
"dataStorage": {
"annotations": { "audit": "ok" },
"labels": { "audit": "ok" },
},
},
}),
),
],
)
}
#[test]
fn unconditional_yaml_fragments_keep_required_source_presence() -> eyre::Result<()> {
assert_chart_cases(
"metrics-server",
vec![
SemanticCase::rejected(
"the fixed tmp volume requires its payload",
"",
json!({ "tmpVolume": null }),
),
SemanticCase::accepted(
"a valid tmp volume payload renders",
json!({ "tmpVolume": { "emptyDir": {} } }),
),
],
)?;
assert_chart_cases(
"traefik",
vec![
SemanticCase::rejected(
"the readiness probe extension cannot be deleted",
"",
json!({ "readinessProbe": null }),
),
SemanticCase::rejected(
"the liveness probe extension cannot be deleted",
"",
json!({ "livenessProbe": null }),
),
SemanticCase::accepted(
"valid probe extensions render",
json!({
"readinessProbe": { "failureThreshold": 1 },
"livenessProbe": { "failureThreshold": 3 },
}),
),
],
)
}
#[test]
fn traefik_ingress_route_keys_must_be_lowercase() -> eyre::Result<()> {
assert_chart_cases(
"traefik",
vec![
SemanticCase::rejected(
"uppercase ingressRoute key",
"/ingressRoute",
json!({ "ingressRoute": { "Audit": { "enabled": false } } }),
),
SemanticCase::accepted(
"lowercase ingressRoute key",
json!({ "ingressRoute": { "audit": { "enabled": false } } }),
),
],
)
}
#[test]
fn sealed_secrets_private_key_metadata_members_must_be_truthy_strings() -> eyre::Result<()> {
assert_chart_cases(
"sealed-secrets",
vec![
SemanticCase::rejected(
"empty-string annotation member",
"/privateKeyAnnotations",
json!({ "privateKeyAnnotations": { "audit": "" } }),
),
SemanticCase::rejected(
"numeric label member",
"/privateKeyLabels",
json!({ "privateKeyLabels": { "audit": 7 } }),
),
SemanticCase::accepted(
"truthy string members",
json!({
"privateKeyAnnotations": { "audit": "ok" },
"privateKeyLabels": { "audit": "ok" }
}),
),
],
)
}
#[test]
fn cilium_backoff_env_name_collisions_are_rejected_while_live() -> eyre::Result<()> {
assert_chart_cases(
"cilium",
vec![
SemanticCase::rejected(
"colliding env name under the default-enabled backoff",
"/extraEnv",
json!({ "extraEnv": [{ "name": "KUBE_CLIENT_BACKOFF_BASE", "value": "1" }] }),
),
SemanticCase::accepted(
"unrelated env name",
json!({ "extraEnv": [{ "name": "AUDIT", "value": "1" }] }),
),
SemanticCase::accepted(
"colliding env name with the backoff disabled",
json!({
"k8sClientExponentialBackoff": { "enabled": false },
"extraEnv": [{ "name": "KUBE_CLIENT_BACKOFF_BASE", "value": "1" }]
}),
),
],
)
}
#[test]
fn bitnami_redis_acl_passwords_reaching_sha256sum_must_be_strings() -> eyre::Result<()> {
assert_chart_cases(
"bitnami-redis",
vec![
SemanticCase::rejected(
"numeric ACL user password",
"/auth/acl/users/0/password",
json!({
"auth": {
"acl": {
"enabled": true,
"users": [{ "username": "audit", "password": 7 }]
}
}
}),
),
SemanticCase::accepted(
"string ACL user password",
json!({
"auth": {
"acl": {
"enabled": true,
"users": [{ "username": "audit", "password": "s3cret" }]
}
}
}),
),
SemanticCase::accepted(
"passwordless ACL user selects nopass",
json!({
"auth": {
"acl": {
"enabled": true,
"users": [{ "username": "audit" }]
}
}
}),
),
],
)
}
#[test]
fn cilium_replacement_list_members_keep_literal_nulls() -> eyre::Result<()> {
let composed = chart_instances::with_override(
"cilium",
json!({
"clustermesh": {
"config": {
"clusters": [{ "name": "c1", "enabled": null, "ips": ["1.1.1.1"] }]
}
}
}),
)?;
sim_assert_eq!(
have: composed.pointer("/clustermesh/config/clusters/0/enabled"),
want: Some(&Value::Null),
"the compositor must not scrub nulls inside replacement lists"
);
assert_chart_cases(
"cilium",
vec![SemanticCase::accepted(
"null enabled member of a replacement cluster list",
json!({
"clustermesh": {
"config": {
"clusters": [{ "name": "c1", "enabled": null, "ips": ["1.1.1.1"] }]
}
}
}),
)],
)
}
#[test]
fn kube_state_metrics_null_namespace_override_composes_to_absent_key() -> eyre::Result<()> {
assert_chart_cases(
"kube-state-metrics",
vec![
SemanticCase::accepted(
"null namespaceOverride deletes the key and selects the release namespace",
json!({ "namespaceOverride": null }),
),
SemanticCase::accepted(
"string namespaceOverride",
json!({ "namespaceOverride": "audit" }),
),
],
)
}
#[test]
fn nats_operator_executes_file_backed_client_auth_template() -> eyre::Result<()> {
assert_chart_cases(
"nats-operator",
vec![
SemanticCase::rejected(
"numeric auth user",
"/cluster/auth/users/0",
json!({ "cluster": { "auth": { "users": [7] } } }),
),
SemanticCase::accepted(
"credential auth user",
json!({
"cluster": {
"auth": {
"users": [{ "username": "audit", "password": "secret" }]
}
}
}),
),
],
)
}
#[test]
fn minio_executes_base_path_template_partials() -> eyre::Result<()> {
assert_chart_cases(
"minio",
vec![
SemanticCase::rejected("numeric bucket", "/buckets/0", json!({ "buckets": [7] })),
SemanticCase::accepted("named bucket", json!({ "buckets": [{ "name": "audit" }] })),
],
)
}
#[test]
fn sealed_secrets_helper_propagates_semver_domain() -> eyre::Result<()> {
assert_chart_cases(
"sealed-secrets",
vec![
SemanticCase::rejected(
"invalid helper-propagated kubeVersion",
"/kubeVersion",
json!({ "kubeVersion": "garbage" }),
),
SemanticCase::accepted(
"valid helper-propagated kubeVersion",
json!({ "kubeVersion": "v1.30.0" }),
),
],
)
}
#[test]
fn cilium_duration_parser_keeps_its_lexical_domain() -> eyre::Result<()> {
assert_chart_cases(
"cilium",
vec![
SemanticCase::rejected(
"invalid conntrack duration",
"/conntrackGCInterval",
json!({ "conntrackGCInterval": "garbage" }),
),
SemanticCase::accepted(
"valid conntrack duration",
json!({ "conntrackGCInterval": "30s" }),
),
],
)
}
#[test]
fn traefik_helper_propagates_semver_domain() -> eyre::Result<()> {
assert_chart_cases(
"traefik",
vec![
SemanticCase::rejected(
"invalid versionOverride",
"/versionOverride",
json!({ "versionOverride": "garbage" }),
),
SemanticCase::accepted(
"valid versionOverride",
json!({ "versionOverride": "v3.7.6" }),
),
],
)
}
#[test]
fn urlquery_total_conversion_accepts_structured_passwords() -> eyre::Result<()> {
assert_chart_cases(
"airflow",
vec![
SemanticCase::accepted(
"map metadata password",
json!({ "data": { "metadataConnection": { "pass": { "key": "value" } } } }),
),
SemanticCase::accepted(
"list metadata password",
json!({ "data": { "metadataConnection": { "pass": ["value"] } } }),
),
SemanticCase::accepted(
"string metadata password",
json!({ "data": { "metadataConnection": { "pass": "secret" } } }),
),
],
)
}
#[test]
fn vault_object_selector_keeps_or_selected_shape_alternatives() -> eyre::Result<()> {
assert_chart_cases(
"vault",
vec![
SemanticCase::accepted(
"preferred structured object selector",
json!({
"injector": {
"webhook": {
"objectSelector": { "matchLabels": { "audit": "true" } }
}
}
}),
),
SemanticCase::accepted(
"preferred templated object selector",
json!({
"injector": {
"webhook": {
"objectSelector": indoc! {"
matchLabels:
audit: {{ .Release.Name | quote }}"}
}
}
}),
),
SemanticCase::accepted(
"legacy templated object selector",
json!({
"injector": {
"webhook": { "objectSelector": "" },
"objectSelector": indoc! {"
matchLabels:
audit: legacy"}
}
}),
),
],
)
}
#[test]
fn vault_quoted_external_address_accepts_total_textual_forms() -> eyre::Result<()> {
assert_chart_cases(
"vault",
vec![
SemanticCase::accepted(
"mapping external Vault address",
json!({ "global": { "externalVaultAddr": { "host": "vault" } } }),
),
SemanticCase::accepted(
"list external Vault address",
json!({ "global": { "externalVaultAddr": ["vault"] } }),
),
SemanticCase::accepted(
"string external Vault address",
json!({ "global": { "externalVaultAddr": "https://vault.example" } }),
),
],
)
}
#[test]
fn prometheus_namespace_text_pipeline_accepts_structured_inputs() -> eyre::Result<()> {
assert_chart_cases(
"prometheus",
vec![
SemanticCase::accepted(
"mapping namespace input",
json!({
"rbac": { "create": true },
"server": {
"namespaces": { "audit": "namespace" },
"useExistingClusterRoleName": "audit"
}
}),
),
SemanticCase::accepted(
"list namespace input",
json!({
"rbac": { "create": true },
"server": {
"namespaces": ["default", "kube-system"],
"useExistingClusterRoleName": "audit"
}
}),
),
],
)
}
#[test]
fn trivy_ignore_policy_prefix_requires_string_values() -> eyre::Result<()> {
assert_chart_cases(
"trivy-operator",
vec![
SemanticCase::rejected(
"mapping ignore policy",
"/trivy/ignorePolicy",
json!({ "trivy": { "ignorePolicy": { "bad": true } } }),
),
SemanticCase::accepted(
"string ignore policy",
json!({ "trivy": { "ignorePolicy": "package trivy" } }),
),
SemanticCase::accepted(
"unrelated dynamic map member",
json!({ "trivy": { "unrelatedAudit": { "bad": true } } }),
),
],
)
}
#[test]
fn traefik_invalid_kind_guard_preserves_falsy_present_values() -> eyre::Result<()> {
assert_chart_cases(
"traefik",
vec![
SemanticCase::accepted(
"falsy present hostUsers",
json!({ "deployment": { "hostUsers": false } }),
),
SemanticCase::accepted(
"truthy Boolean hostUsers",
json!({ "deployment": { "hostUsers": true } }),
),
],
)
}
#[test]
fn cilium_kube_proxy_replacement_accepts_raw_booleans() -> eyre::Result<()> {
assert_chart_cases(
"cilium",
vec![
SemanticCase::accepted("raw Boolean true", json!({ "kubeProxyReplacement": true })),
SemanticCase::accepted(
"raw Boolean false",
json!({ "kubeProxyReplacement": false }),
),
SemanticCase::accepted("string spelling", json!({ "kubeProxyReplacement": "true" })),
SemanticCase::accepted(
"empty string selects the coalesce default",
json!({ "kubeProxyReplacement": "" }),
),
SemanticCase::accepted(
"null folds to empty and selects the coalesce default",
json!({ "kubeProxyReplacement": null }),
),
SemanticCase::accepted(
"the literal <nil> spelling folds to empty too",
json!({ "kubeProxyReplacement": "<nil>" }),
),
SemanticCase::rejected(
"legacy strict spelling aborts the render",
"",
json!({ "kubeProxyReplacement": "strict" }),
),
SemanticCase::rejected(
"numeric value stringifies to a rejected spelling",
"",
json!({ "kubeProxyReplacement": 1 }),
),
],
)
}
#[test]
fn cilium_removed_options_abort_even_when_disabled() -> eyre::Result<()> {
assert_chart_cases(
"cilium",
vec![
SemanticCase::accepted(
"absent leaf renders",
json!({ "proxy": { "prometheus": {} } }),
),
SemanticCase::accepted(
"raw empty string stringifies to the falsy empty rendering",
json!({ "proxy": { "prometheus": { "enabled": "" } } }),
),
SemanticCase::rejected(
"explicitly disabled still aborts (truthy \"false\")",
"/proxy",
json!({ "proxy": { "prometheus": { "enabled": false } } }),
),
SemanticCase::rejected(
"enabled aborts",
"/proxy",
json!({ "proxy": { "prometheus": { "enabled": true } } }),
),
SemanticCase::rejected(
"the sibling port disjunct keeps Helm truthiness",
"/proxy",
json!({ "proxy": { "prometheus": { "port": 9095 } } }),
),
],
)
}
#[test]
fn promtail_extra_port_members_require_the_container_port() -> eyre::Result<()> {
assert_chart_cases(
"promtail",
vec![
SemanticCase::rejected(
"an empty member renders null ports",
"/extraPorts",
json!({ "extraPorts": { "audit": {} } }),
),
SemanticCase::rejected(
"service.port alone leaves the pod port null",
"/extraPorts",
json!({ "extraPorts": { "audit": { "service": { "port": 80 } } } }),
),
SemanticCase::accepted(
"containerPort fills both sinks",
json!({ "extraPorts": { "audit": { "containerPort": 1234 } } }),
),
SemanticCase::accepted(
"containerPort beside a service port",
json!({ "extraPorts": { "audit": { "containerPort": 1234,
"service": { "port": 80 } } } }),
),
],
)
}
#[test]
fn kube_state_metrics_probe_headers_require_name_and_value() -> eyre::Result<()> {
assert_chart_cases(
"kube-state-metrics",
vec![
SemanticCase::rejected(
"an empty header member renders null name and value",
"/startupProbe/httpGet/httpHeaders",
json!({ "startupProbe": { "enabled": true,
"httpGet": { "httpHeaders": [{}] } } }),
),
SemanticCase::accepted(
"populated headers render",
json!({ "startupProbe": { "enabled": true,
"httpGet": { "httpHeaders":
[{ "name": "X-Audit", "value": "audit" }] } } }),
),
SemanticCase::accepted(
"the disabled probe renders nothing",
json!({ "startupProbe": { "httpGet": { "httpHeaders": [{}] } } }),
),
],
)
}
#[test]
fn kyverno_zero_replicas_abort_through_the_template_helper() -> eyre::Result<()> {
assert_chart_cases(
"kyverno",
vec![
SemanticCase::rejected(
"admission controller zero replicas abort",
"/admissionController/replicas",
json!({ "admissionController": { "replicas": 0 } }),
),
SemanticCase::rejected(
"reports controller zero replicas abort",
"/reportsController/replicas",
json!({ "reportsController": { "replicas": 0 } }),
),
SemanticCase::accepted(
"nonzero replicas render",
json!({ "admissionController": { "replicas": 2 } }),
),
SemanticCase::accepted(
"a string spelling escapes the kind dispatch",
json!({ "admissionController": { "replicas": "0" } }),
),
],
)
}
#[test]
fn oauth2_proxy_redis_ha_config_members_render_as_block_text() -> eyre::Result<()> {
assert_chart_cases(
"oauth2-proxy",
vec![
SemanticCase::accepted(
"string config members render as block text",
json!({ "redis-ha": { "enabled": true,
"redis": { "config": { "maxmemory": "100mb" } } } }),
),
SemanticCase::accepted(
"raw scalars stringify in the loop body",
json!({ "redis-ha": { "enabled": true,
"redis": { "config": { "repl-diskless-sync": true } } } }),
),
SemanticCase::accepted(
"string custom config renders through tpl",
json!({ "redis-ha": { "enabled": true,
"redis": { "customConfig": "maxmemory 100mb" } } }),
),
SemanticCase::rejected(
"tpl still requires a string program",
"/redis-ha/redis/customConfig",
json!({ "redis-ha": { "enabled": true,
"redis": { "customConfig": { "bad": true } } } }),
),
],
)
}
#[test]
fn oauth2_proxy_standalone_redis_requires_a_connection_url() -> eyre::Result<()> {
assert_chart_cases(
"oauth2-proxy",
vec![
SemanticCase::rejected(
"standalone client without a connection url aborts",
"",
json!({ "sessionStorage": { "type": "redis",
"redis": { "clientType": "standalone" } } }),
),
SemanticCase::accepted(
"an explicit connection url renders",
json!({ "sessionStorage": { "type": "redis",
"redis": { "clientType": "standalone",
"standalone": { "connectionUrl": "redis://myredis:6379" } } } }),
),
SemanticCase::accepted(
"the enabled redis subchart computes the url",
json!({ "sessionStorage": { "type": "redis",
"redis": { "clientType": "standalone" } },
"redis-ha": { "enabled": true } }),
),
],
)
}
#[test]
fn argo_cd_redis_ha_own_defaults_render_when_enabled() -> eyre::Result<()> {
assert_chart_cases(
"argo-cd",
vec![SemanticCase::accepted(
"enabling redis-ha keeps the chart's own config defaults",
json!({ "redis-ha": { "enabled": true } }),
)],
)
}
#[test]
fn traefik_otlp_resource_attributes_render_as_flag_loops() -> eyre::Result<()> {
assert_chart_cases(
"traefik",
vec![
SemanticCase::accepted(
"string members render as flags",
json!({ "tracing": { "otlp": { "enabled": true, "resourceAttributes": { "env": "prod" } } } }),
),
SemanticCase::accepted(
"non-string members stringify in the loop body",
json!({ "tracing": { "otlp": { "enabled": true, "resourceAttributes": { "env": 7 } } } }),
),
SemanticCase::accepted(
"a list is rangeable too",
json!({ "tracing": { "otlp": { "enabled": true, "resourceAttributes": ["a"] } } }),
),
SemanticCase::accepted(
"metrics rides the same helper",
json!({ "metrics": { "otlp": { "enabled": true, "resourceAttributes": { "env": "prod" } } } }),
),
SemanticCase::rejected(
"a scalar is not rangeable",
"/tracing/otlp/resourceAttributes",
json!({ "tracing": { "otlp": { "enabled": true, "resourceAttributes": 7 } } }),
),
],
)
}
#[test]
fn cilium_certificate_sans_require_string_members() -> eyre::Result<()> {
assert_chart_cases(
"cilium",
vec![
SemanticCase::rejected(
"numeric Hubble IP SAN",
"/hubble/tls/server/extraIpAddresses/0",
json!({
"hubble": { "tls": { "server": { "extraIpAddresses": [7] } } }
}),
),
SemanticCase::rejected(
"numeric Hubble DNS SAN",
"/hubble/tls/server/extraDnsNames/0",
json!({
"hubble": { "tls": { "server": { "extraDnsNames": [7] } } }
}),
),
SemanticCase::rejected(
"non-address Hubble IP SAN",
"/hubble/tls/server/extraIpAddresses/0",
json!({
"hubble": { "tls": { "server": { "extraIpAddresses": ["not-an-ip"] } } }
}),
),
SemanticCase::rejected(
"bare-colon Hubble IP SAN",
"/hubble/tls/server/extraIpAddresses/0",
json!({
"hubble": { "tls": { "server": { "extraIpAddresses": [":"] } } }
}),
),
SemanticCase::rejected(
"zoned Hubble IP SAN",
"/hubble/tls/server/extraIpAddresses/0",
json!({
"hubble": { "tls": { "server": { "extraIpAddresses": ["fe80::1%eth0"] } } }
}),
),
SemanticCase::accepted(
"string Hubble SANs",
json!({
"hubble": {
"tls": {
"server": {
"extraIpAddresses": [
"10.0.0.7",
"2001:db8::1",
"::ffff:10.0.0.7",
"1:2:3:4:5:6:1.2.3.4",
"1:2:3:4:5:6:7::"
],
"extraDnsNames": ["audit.example"]
}
}
}
}),
),
],
)
}
#[test]
fn loki_minio_user_index_requires_a_first_user_when_live() -> eyre::Result<()> {
let live_enterprise = json!({
"enterprise": { "enabled": true },
"loki": {
"storage": {
"bucketNames": { "admin": "admin", "chunks": "chunks", "ruler": "ruler" }
},
"useTestSchema": true
},
"minio": { "enabled": true }
});
assert_chart_cases(
"loki",
vec![
SemanticCase::rejected(
"empty MinIO users in live enterprise gateway",
"/minio/users",
json!({
"enterprise": { "enabled": true },
"loki": {
"storage": {
"bucketNames": {
"admin": "admin",
"chunks": "chunks",
"ruler": "ruler"
}
},
"useTestSchema": true
},
"minio": { "enabled": true, "users": [] }
}),
),
SemanticCase::accepted("first MinIO user available", live_enterprise),
SemanticCase::accepted(
"empty MinIO users while enterprise gateway is dormant",
json!({
"loki": { "useTestSchema": true },
"minio": { "enabled": false, "users": [] }
}),
),
],
)
}
#[test]
fn loki_chart_authored_htpasswd_program_requires_selected_credentials() -> eyre::Result<()> {
let live = |basic_auth: Value| {
json!({
"gateway": { "basicAuth": basic_auth },
"loki": {
"storage": {
"bucketNames": {
"admin": "admin",
"chunks": "chunks",
"ruler": "ruler"
}
},
"useTestSchema": true
}
})
};
assert_chart_cases(
"loki",
vec![
SemanticCase::rejected(
"selected default htpasswd program without username",
"",
live(json!({ "enabled": true, "password": "pass" })),
),
SemanticCase::rejected(
"selected default htpasswd program without password",
"",
live(json!({ "enabled": true, "username": "user" })),
),
SemanticCase::accepted(
"selected default htpasswd program with credentials",
live(json!({
"enabled": true,
"username": "user",
"password": "pass"
})),
),
SemanticCase::accepted(
"literal htpasswd override replaces the default program",
live(json!({ "enabled": true, "htpasswd": "audit:hash" })),
),
],
)
}
#[test]
fn coredns_prometheus_address_requires_a_host_port_split() -> eyre::Result<()> {
let server = |parameters| {
json!([{
"zones": [{ "zone": "." }],
"port": 53,
"plugins": [{ "name": "prometheus", "parameters": parameters }]
}])
};
assert_chart_cases(
"coredns",
vec![
SemanticCase::rejected(
"one-segment Prometheus address",
"/servers/0/plugins/0/parameters",
json!({ "servers": server("9153") }),
),
SemanticCase::accepted(
"host and port Prometheus address",
json!({ "servers": server("0.0.0.0:9153") }),
),
],
)
}
#[test]
fn falco_removed_config_accumulator_forbids_present_legacy_keys() -> eyre::Result<()> {
assert_chart_cases(
"falco",
vec![
SemanticCase::rejected(
"empty removed driver key is still present",
"",
json!({ "driver": { "ebpf": {} } }),
),
SemanticCase::rejected(
"falsy removed Falco key is still present",
"",
json!({ "falco": { "grpc": false } }),
),
SemanticCase::accepted("removed keys absent", json!({})),
],
)
}
#[test]
fn jaeger_http_route_validator_requires_a_parent_reference() -> eyre::Result<()> {
assert_chart_cases(
"jaeger",
vec![
SemanticCase::rejected(
"enabled HTTPRoute without parent references",
"/jaeger",
json!({ "jaeger": { "httproute": { "enabled": true, "parentRefs": [] } } }),
),
SemanticCase::accepted(
"enabled HTTPRoute with a parent reference",
json!({
"jaeger": {
"httproute": {
"enabled": true,
"parentRefs": [{ "name": "gateway" }]
}
}
}),
),
],
)
}
#[test]
fn flux2_controller_tags_require_substr_string_subjects() -> eyre::Result<()> {
assert_chart_cases(
"flux2",
vec![
SemanticCase::rejected(
"mapping kustomize-controller tag",
"/kustomizeController/tag",
json!({ "kustomizeController": { "tag": { "bad": true } } }),
),
SemanticCase::rejected("list CLI tag", "/cli/tag", json!({ "cli": { "tag": [1] } })),
SemanticCase::accepted(
"plain version tag",
json!({ "kustomizeController": { "tag": "v1.2.3" } }),
),
SemanticCase::accepted("digest tag", json!({ "cli": { "tag": "sha256:0123abcd" } })),
],
)
}
#[test]
fn cilium_upgrade_compatibility_keeps_helm_empty_inputs_open() -> eyre::Result<()> {
assert_chart_cases(
"cilium",
vec![
SemanticCase::accepted(
"false selects the fallback",
json!({ "upgradeCompatibility": false }),
),
SemanticCase::accepted(
"empty map selects the fallback",
json!({ "upgradeCompatibility": {} }),
),
SemanticCase::accepted(
"plain version string",
json!({ "upgradeCompatibility": "1.14" }),
),
SemanticCase::rejected(
"truthy non-semver string",
"/upgradeCompatibility",
json!({ "upgradeCompatibility": "garbage" }),
),
SemanticCase::rejected(
"truthy map reaches the parser",
"/upgradeCompatibility",
json!({ "upgradeCompatibility": { "a": 1 } }),
),
],
)
}
#[test]
fn cloudnative_pg_override_fallbacks_keep_helm_empty_inputs_open() -> eyre::Result<()> {
assert_chart_cases(
"cloudnative-pg",
vec![
SemanticCase::accepted("false name override", json!({ "nameOverride": false })),
SemanticCase::accepted("empty map name override", json!({ "nameOverride": {} })),
SemanticCase::accepted("string name override", json!({ "nameOverride": "custom" })),
SemanticCase::rejected(
"truthy map survives selection and aborts trunc",
"/nameOverride",
json!({ "nameOverride": { "a": 1 } }),
),
SemanticCase::accepted(
"false namespace override is skipped",
json!({ "namespaceOverride": false }),
),
SemanticCase::accepted(
"string namespace override",
json!({ "namespaceOverride": "ns" }),
),
],
)
}
#[test]
fn bitnami_redis_auth_ternary_holds_across_architecture_partitions() -> eyre::Result<()> {
assert_chart_cases(
"bitnami-redis",
vec![
SemanticCase::rejected(
"replication string auth.enabled",
"/auth/enabled",
json!({ "auth": { "enabled": "true" } }),
),
SemanticCase::rejected(
"standalone string auth.enabled",
"/auth/enabled",
json!({ "architecture": "standalone", "auth": { "enabled": "true" } }),
),
SemanticCase::accepted(
"standalone Boolean auth.enabled",
json!({ "architecture": "standalone", "auth": { "enabled": true } }),
),
SemanticCase::accepted(
"replication Boolean auth.enabled",
json!({ "auth": { "enabled": true } }),
),
SemanticCase::accepted(
"scaled-to-zero master never runs the ternary",
json!({
"architecture": "standalone",
"master": { "count": 0 },
"auth": { "enabled": "true" }
}),
),
],
)
}
#[test]
fn velero_schedule_members_carry_the_crd_member_schema() -> eyre::Result<()> {
assert_chart_cases(
"velero",
vec![
SemanticCase::rejected(
"string paused on a ranged member",
"/schedules/audit/paused",
json!({ "schedules": { "audit": { "schedule": "0 0 * * *", "paused": "audit" } } }),
),
SemanticCase::accepted(
"Boolean paused on a ranged member",
json!({ "schedules": { "audit": { "schedule": "0 0 * * *", "paused": true } } }),
),
SemanticCase::rejected(
"invalid hook onError enum",
"/schedules/audit/template",
json!({
"schedules": { "audit": { "schedule": "0 0 * * *", "template": {
"hooks": { "resources": [{ "name": "h", "post": [{
"exec": { "command": ["/bin/x"], "onError": "audit" }
}] }] }
} } }
}),
),
SemanticCase::accepted(
"valid hook onError enum",
json!({
"schedules": { "audit": { "schedule": "0 0 * * *", "template": {
"hooks": { "resources": [{ "name": "h", "post": [{
"exec": { "command": ["/bin/x"], "onError": "Fail" }
}] }] }
} } }
}),
),
SemanticCase::accepted(
"disabled member skips the document",
json!({ "schedules": { "audit": { "disabled": true, "paused": "audit" } } }),
),
],
)
}
#[test]
fn cluster_autoscaler_env_config_map_members_stay_open_with_contracts() -> eyre::Result<()> {
let live = json!({ "autoDiscovery": { "clusterName": "audit-cluster" } });
let merge = |extra: serde_json::Value| {
let mut base = live.clone();
base.as_object_mut()
.expect("object")
.insert("extraEnvConfigMaps".to_string(), extra);
base
};
assert_chart_cases(
"cluster-autoscaler",
vec![
SemanticCase::accepted(
"complete dynamic member",
merge(json!({ "AUDIT": { "name": "cfg", "key": "value" } })),
),
SemanticCase::rejected(
"member missing the required key",
"/extraEnvConfigMaps",
merge(json!({ "AUDIT": { "name": "cfg" } })),
),
SemanticCase::rejected(
"scalar member cannot host field reads",
"/extraEnvConfigMaps",
merge(json!({ "AUDIT": 7 })),
),
],
)
}
#[test]
fn prometheus_remote_write_urls_require_strings() -> eyre::Result<()> {
assert_chart_cases(
"prometheus",
vec![
SemanticCase::rejected(
"numeric remoteWrite url",
"/server/remoteWrite",
json!({ "server": { "remoteWrite": [{ "url": 7 }] } }),
),
SemanticCase::rejected(
"member missing url",
"/server/remoteWrite",
json!({ "server": { "remoteWrite": [{ "name": "x" }] } }),
),
SemanticCase::accepted(
"string remoteWrite url",
json!({ "server": { "remoteWrite": [{ "url": "http://mimir/push" }] } }),
),
],
)
}
#[test]
fn datadog_cluster_agent_latest_tag_survives_the_version_check() -> eyre::Result<()> {
assert_chart_cases(
"datadog",
vec![
SemanticCase::accepted(
"the latest sentinel is reassigned before parsing",
json!({ "clusterAgent": { "image": { "tag": "latest" } } }),
),
SemanticCase::accepted(
"ordinary version tag",
json!({ "clusterAgent": { "image": { "tag": "1.26.0" } } }),
),
SemanticCase::rejected(
"non-sentinel invalid version",
"/clusterAgent/image/tag",
json!({ "clusterAgent": { "image": { "tag": "garbage" } } }),
),
SemanticCase::accepted(
"doNotCheckTag skips the check entirely",
json!({ "clusterAgent": { "image": { "tag": "garbage", "doNotCheckTag": true } } }),
),
],
)
}
#[test]
fn datadog_otel_gateway_empty_tag_selects_the_agent_version_fallback() -> eyre::Result<()> {
assert_chart_cases(
"datadog",
vec![
SemanticCase::accepted(
"empty tag takes the fallback",
json!({ "otelAgentGateway": { "enabled": true, "image": { "tag": "" } } }),
),
SemanticCase::accepted(
"null tag takes the fallback",
json!({ "otelAgentGateway": { "enabled": true, "image": { "tag": null } } }),
),
SemanticCase::accepted(
"explicit valid tag",
json!({ "otelAgentGateway": { "enabled": true, "image": { "tag": "7.70.0" } } }),
),
SemanticCase::rejected(
"truthy non-version tag reaches the parser",
"/otelAgentGateway/image/tag",
json!({ "otelAgentGateway": { "enabled": true, "image": { "tag": "junk" } } }),
),
],
)
}
#[test]
fn datadog_otlp_grpc_endpoints_reject_the_unix_protocol() -> eyre::Result<()> {
assert_chart_cases(
"datadog",
vec![
SemanticCase::rejected(
"a unix endpoint with a port suffix aborts on the prefix",
"",
json!({ "datadog": { "apiKey": "dummykey", "otlp": { "receiver": {
"protocols": { "grpc": { "enabled": true,
"endpoint": "unix:///tmp/otlp.sock:4317" } } } } } }),
),
SemanticCase::rejected(
"a portless endpoint aborts",
"/datadog/otlp/receiver/protocols/grpc/endpoint",
json!({ "datadog": { "apiKey": "dummykey", "otlp": { "receiver": {
"protocols": { "grpc": { "enabled": true,
"endpoint": "0.0.0.0" } } } } } }),
),
SemanticCase::accepted(
"a host:port endpoint renders",
json!({ "datadog": { "apiKey": "dummykey", "otlp": { "receiver": {
"protocols": { "grpc": { "enabled": true,
"endpoint": "0.0.0.0:4317" } } } } } }),
),
SemanticCase::accepted(
"the disabled receiver keeps any endpoint open",
json!({ "datadog": { "apiKey": "dummykey", "otlp": { "receiver": {
"protocols": { "grpc": { "enabled": false,
"endpoint": "unix:///tmp/otlp.sock" } } } } } }),
),
],
)
}
#[test]
fn traefik_local_plugins_keep_their_alternative_shapes() -> eyre::Result<()> {
assert_chart_cases(
"traefik",
vec![
SemanticCase::accepted(
"legacy hostPath shape",
json!({ "experimental": { "localPlugins": { "my-plugin": {
"moduleName": "github.com/x/y", "hostPath": "/plugins/y",
"mountPath": "/plugins-storage/y"
} } } }),
),
SemanticCase::accepted(
"inline plugin shape",
json!({ "experimental": { "localPlugins": { "my-plugin": {
"moduleName": "github.com/x/y",
"type": "inlinePlugin",
"mountPath": "/plugins-storage/y",
"source": { "main.go": "package main" }
} } } }),
),
SemanticCase::accepted(
"typed hostPath shape",
json!({ "experimental": { "localPlugins": { "my-plugin": {
"moduleName": "github.com/x/y",
"type": "hostPath",
"hostPath": "/plugins/y",
"mountPath": "/plugins-storage/y"
} } } }),
),
SemanticCase::rejected(
"unknown type aborts even beside a hostPath",
"/experimental/localPlugins",
json!({ "experimental": { "localPlugins": { "my-plugin": {
"moduleName": "github.com/x/y", "type": "bogus", "hostPath": "/x"
} } } }),
),
SemanticCase::rejected(
"neither type nor hostPath aborts",
"/experimental/localPlugins",
json!({ "experimental": { "localPlugins": { "my-plugin": {
"moduleName": "github.com/x/y"
} } } }),
),
],
)
}
#[test]
fn traefik_transformed_tag_sentinels_survive_the_version_checks() -> eyre::Result<()> {
assert_chart_cases(
"traefik",
vec![
SemanticCase::accepted(
"latest- prefixed version",
json!({ "image": { "tag": "latest-v3.6.0" } }),
),
SemanticCase::accepted(
"experimental- prefixed version",
json!({ "image": { "tag": "experimental-v3.6.0" } }),
),
SemanticCase::accepted(
"master appVersion sentinel",
json!({ "image": { "tag": "master" } }),
),
SemanticCase::accepted(
"digest-suffixed version",
json!({ "image": { "tag": "v3.6.0@sha256:0123abcd" } }),
),
SemanticCase::accepted("plain version", json!({ "image": { "tag": "v3.6.0" } })),
SemanticCase::rejected(
"bare latest is not stripped",
"/image/tag",
json!({ "image": { "tag": "latest" } }),
),
SemanticCase::rejected(
"untouched non-version string",
"/image/tag",
json!({ "image": { "tag": "audit" } }),
),
],
)
}
#[test]
fn zalando_operator_quoted_image_scalar_excludes_raw_quotes() -> eyre::Result<()> {
assert_chart_cases(
"zalando-postgres-operator",
vec![
SemanticCase::rejected(
"registry with embedded double quote",
"/image/registry",
json!({ "image": { "registry": "bad\"quote" } }),
),
SemanticCase::rejected(
"registry with backslash escape",
"/image/registry",
json!({ "image": { "registry": "back\\slash" } }),
),
SemanticCase::accepted(
"ordinary registry",
json!({ "image": { "registry": "ghcr.io" } }),
),
SemanticCase::accepted("numeric registry", json!({ "image": { "registry": 7 } })),
],
)
}
#[test]
fn tempo_assembled_image_scalar_excludes_token_initial_lists() -> eyre::Result<()> {
assert_chart_cases(
"tempo",
vec![
SemanticCase::rejected(
"list registry",
"/tempo/registry",
json!({ "tempo": { "registry": ["a", "b"] } }),
),
SemanticCase::rejected(
"empty list registry",
"/tempo/registry",
json!({ "tempo": { "registry": [] } }),
),
SemanticCase::accepted(
"plain registry",
json!({ "tempo": { "registry": "docker.io" } }),
),
SemanticCase::accepted(
"map registry renders as plain text",
json!({ "tempo": { "registry": { "a": "b" } } }),
),
],
)
}
#[test]
fn flux2_prefixed_log_level_accepts_every_input_kind() -> eyre::Result<()> {
assert_chart_cases(
"flux2",
vec![
SemanticCase::accepted("map log level", json!({ "logLevel": { "a": "b" } })),
SemanticCase::accepted("list log level", json!({ "logLevel": ["a"] })),
SemanticCase::accepted("false selects the default", json!({ "logLevel": false })),
SemanticCase::accepted("plain log level", json!({ "logLevel": "info" })),
],
)
}
#[test]
fn aws_lbc_null_spelling_name_override_stays_rejected() -> eyre::Result<()> {
assert_chart_cases(
"aws-load-balancer-controller",
vec![
SemanticCase::rejected(
"null-spelling name override",
"/nameOverride",
json!({ "clusterName": "test", "nameOverride": "null" }),
),
SemanticCase::accepted(
"ordinary name override",
json!({ "clusterName": "test", "nameOverride": "ok-name" }),
),
],
)
}
#[test]
fn tpl_serialized_fragments_keep_their_structural_slots() -> eyre::Result<()> {
assert_chart_cases(
"cloudnative-pg",
vec![
SemanticCase::rejected(
"scalar additionalEnv breaks the env sequence",
"/additionalEnv",
json!({ "additionalEnv": 7 }),
),
SemanticCase::accepted(
"EnvVar list renders",
json!({ "additionalEnv": [{ "name": "A", "value": "b" }] }),
),
SemanticCase::accepted(
"templated EnvVar value renders",
json!({ "additionalEnv": [{ "name": "A", "value": "{{ .Release.Name }}" }] }),
),
],
)?;
assert_chart_cases(
"airflow",
vec![
SemanticCase::accepted(
"list scheduler command",
json!({ "scheduler": { "command": ["bash"] } }),
),
SemanticCase::accepted(
"null command selects the image default",
json!({ "scheduler": { "command": null } }),
),
],
)
}
#[test]
fn external_secrets_pdb_header_member_requires_an_object_host() -> eyre::Result<()> {
assert_chart_cases(
"external-secrets",
vec![
SemanticCase::rejected(
"integer podDisruptionBudget",
"/webhook/podDisruptionBudget",
json!({ "webhook": { "podDisruptionBudget": 7 } }),
),
SemanticCase::rejected(
"array podDisruptionBudget",
"/webhook/podDisruptionBudget",
json!({ "webhook": { "podDisruptionBudget": [1] } }),
),
SemanticCase::rejected(
"boolean podDisruptionBudget",
"/webhook/podDisruptionBudget",
json!({ "webhook": { "podDisruptionBudget": true } }),
),
SemanticCase::accepted(
"object podDisruptionBudget",
json!({ "webhook": { "podDisruptionBudget": { "enabled": false } } }),
),
],
)
}
#[test]
fn external_secrets_omitted_security_context_keys_scope_their_typing() -> eyre::Result<()> {
assert_chart_cases(
"external-secrets",
vec![
SemanticCase::accepted(
"force mode removes runAsUser before the sink",
json!({
"global": { "compatibility": { "openshift": { "adaptSecurityContext": "force" } } },
"securityContext": { "runAsUser": "audit" },
}),
),
SemanticCase::accepted(
"auto mode is cluster-dependent, so the key stays open",
json!({ "securityContext": { "runAsUser": "audit" } }),
),
SemanticCase::rejected(
"disabled mode keeps the key on the rendered Deployment",
"/securityContext/runAsUser",
json!({
"global": { "compatibility": { "openshift": { "adaptSecurityContext": "disabled" } } },
"securityContext": { "runAsUser": "audit" },
}),
),
SemanticCase::accepted(
"disabled mode with a valid integer",
json!({
"global": { "compatibility": { "openshift": { "adaptSecurityContext": "disabled" } } },
"securityContext": { "runAsUser": 1000 },
}),
),
SemanticCase::accepted(
"a disabled render gate never reaches the provider slot",
json!({
"global": { "compatibility": { "openshift": { "adaptSecurityContext": "disabled" } } },
"securityContext": { "enabled": false, "runAsUser": "audit" },
}),
),
SemanticCase::rejected(
"a never-omitted key keeps its typing even under force",
"/securityContext/runAsNonRoot",
json!({
"global": { "compatibility": { "openshift": { "adaptSecurityContext": "force" } } },
"securityContext": { "runAsNonRoot": "audit" },
}),
),
],
)
}
#[test]
fn cilium_hubble_ui_tag_binds_the_transformed_semver_bound() -> eyre::Result<()> {
let with_tag = |tag: &str| {
json!({
"hubble": {
"relay": { "enabled": true },
"ui": { "enabled": true, "backend": { "image": { "tag": tag } } },
},
})
};
assert_chart_cases(
"cilium",
vec![
SemanticCase::accepted("v-prefixed valid version", with_tag("v0.13.5")),
SemanticCase::accepted(
"digest-suffixed valid version",
with_tag("v0.13.5@sha256:abc"),
),
SemanticCase::accepted("the ne-excluded latest sentinel", with_tag("latest")),
SemanticCase::rejected(
"unparsable core aborts semverCompare",
"/hubble/ui/backend/image/tag",
with_tag("garbage"),
),
SemanticCase::rejected(
"version below the bound hits the fail",
"/hubble/ui/backend/image/tag",
with_tag("v0.1.0"),
),
],
)
}
#[test]
fn external_secrets_deleted_render_mode_selects_the_default_literal_arm() -> eyre::Result<()> {
assert_chart_cases(
"external-secrets",
vec![
SemanticCase::accepted(
"null renderMode deletes the key and selects the default",
json!({ "serviceMonitor": { "renderMode": null } }),
),
SemanticCase::accepted(
"explicit non-default mode",
json!({ "serviceMonitor": { "renderMode": "alwaysRender" } }),
),
SemanticCase::rejected(
"unlisted mode aborts every render",
"",
json!({ "serviceMonitor": { "renderMode": "junkMode" } }),
),
],
)
}
#[test]
fn oauth2_proxy_legacy_extra_paths_abort_under_the_v1_ingress_api() -> eyre::Result<()> {
assert_chart_cases(
"oauth2-proxy",
vec![
SemanticCase::rejected(
"legacy serviceName under a pinned modern kubeVersion",
"/ingress/extraPaths",
json!({ "kubeVersion": "1.30.0", "ingress": { "enabled": true,
"hosts": ["h.example"], "extraPaths": [
{ "path": "/*", "backend": { "serviceName": "ssl-redirect" } }
] } }),
),
SemanticCase::rejected(
"legacy servicePort under a pinned modern kubeVersion",
"/ingress/extraPaths",
json!({ "kubeVersion": "1.30.0", "ingress": { "enabled": true,
"hosts": ["h.example"], "extraPaths": [
{ "path": "/*", "backend": { "servicePort": "use-annotation" } }
] } }),
),
SemanticCase::accepted(
"legacy format renders on the v1beta1 api",
json!({ "kubeVersion": "1.18.0", "ingress": { "enabled": true,
"hosts": ["h.example"], "extraPaths": [
{ "path": "/*", "backend": { "serviceName": "ssl-redirect",
"servicePort": "use-annotation" } }
] } }),
),
SemanticCase::accepted(
"modern v1 backend",
json!({ "kubeVersion": "1.30.0", "ingress": { "enabled": true,
"hosts": ["h.example"], "extraPaths": [
{ "path": "/*", "pathType": "ImplementationSpecific",
"backend": { "service": { "name": "ssl-redirect",
"port": { "name": "use-annotation" } } } }
] } }),
),
SemanticCase::rejected(
"policy kubeVersion selects v1 without a chart override",
"/ingress/extraPaths",
json!({ "ingress": { "enabled": true, "hosts": ["h.example"],
"extraPaths": [
{ "path": "/*", "backend": { "serviceName": "ssl-redirect" } }
] } }),
),
SemanticCase::accepted(
"checkDeprecation disabled tolerates the legacy format",
json!({ "checkDeprecation": false, "kubeVersion": "1.30.0",
"ingress": { "enabled": true, "hosts": ["h.example"],
"extraPaths": [
{ "path": "/*", "backend": { "serviceName": "ssl-redirect" } }
] } }),
),
],
)
}
#[test]
fn signoz_additional_env_members_stay_open_under_dedup_shadowing() -> eyre::Result<()> {
assert_chart_cases(
"signoz-signoz",
vec![
SemanticCase::rejected(
"a singleton member cannot be shadowed, so the EnvVar shape binds",
"/signoz/additionalEnvs",
json!({ "signoz": { "additionalEnvs": { "AUDIT": { "value": 7 } } } }),
),
SemanticCase::accepted(
"a valid singleton EnvVar member",
json!({ "signoz": { "additionalEnvs": { "AUDIT": { "value": "ok" } } } }),
),
SemanticCase::accepted(
"case-colliding shadowed member renders nothing",
json!({ "signoz": { "additionalEnvs": {
"AUDIT": { "value": "ok" }, "audit": { "value": 7 }
} } }),
),
SemanticCase::accepted(
"scalar member renders through the quoted value lane",
json!({ "signoz": { "additionalEnvs": { "AUDIT": 7 } } }),
),
],
)
}
#[test]
fn minio_environment_list_lane_is_excluded_at_the_name_slot() -> eyre::Result<()> {
assert_chart_cases(
"minio",
vec![
SemanticCase::rejected(
"non-empty list supplies integer keys",
"/environment",
json!({ "environment": ["audit"] }),
),
SemanticCase::accepted(
"map keys are strings",
json!({ "environment": { "AUDIT": "ok" } }),
),
SemanticCase::accepted(
"empty list runs zero iterations",
json!({ "environment": [] }),
),
],
)
}
#[test]
fn airflow_celery_broker_sentinel_requires_a_matching_env_item() -> eyre::Result<()> {
let celery = |extra: serde_json::Value| {
let mut base = json!({
"executor": "CeleryExecutor",
"redis": { "enabled": true, "passwordSecretName": "redis-secret" }
});
for (key, value) in extra.as_object().expect("object") {
base.as_object_mut()
.expect("object")
.insert(key.clone(), value.clone());
}
base
};
assert_chart_cases(
"airflow",
vec![
SemanticCase::rejected("no broker source terminates", "", celery(json!({}))),
SemanticCase::rejected(
"a differently named env item terminates",
"",
celery(json!({ "env": [{ "name": "OTHER", "value": "x" }] })),
),
SemanticCase::accepted(
"brokerUrlSecretName satisfies the sentinel",
celery(json!({ "data": { "brokerUrlSecretName": "broker-secret" } })),
),
SemanticCase::accepted(
"a matching env item satisfies the sentinel",
celery(
json!({ "env": [{ "name": "AIRFLOW__CELERY__BROKER_URL_CMD", "value": "cmd" }] }),
),
),
],
)
}
#[test]
fn cilium_scalar_domain_validators_reject_out_of_domain_values() -> eyre::Result<()> {
assert_chart_cases(
"cilium",
vec![
SemanticCase::rejected(
"cluster name over 32 characters",
"/cluster/name",
json!({ "cluster": { "name": "a".repeat(40) } }),
),
SemanticCase::accepted(
"cluster name within the bound",
json!({ "cluster": { "name": "good-name" } }),
),
SemanticCase::rejected(
"kvstoreMode outside the literal membership",
"/clustermesh/apiserver/kvstoremesh",
json!({ "clustermesh": { "apiserver": { "kvstoremesh": { "kvstoreMode": "bogus" } } } }),
),
SemanticCase::accepted(
"kvstoreMode in the membership",
json!({ "clustermesh": { "apiserver": { "kvstoremesh": { "kvstoreMode": "internal" } } } }),
),
SemanticCase::rejected(
"maxConnectedClusters outside 255-or-511",
"/clustermesh",
json!({ "clustermesh": { "maxConnectedClusters": 300 } }),
),
SemanticCase::accepted(
"maxConnectedClusters at the alternate bound",
json!({ "clustermesh": { "maxConnectedClusters": 511 } }),
),
SemanticCase::accepted(
"numeric string stays outside the raw-integer subset",
json!({ "clustermesh": { "maxConnectedClusters": "255" } }),
),
],
)
}
#[test]
fn unset_helper_contexts_preserve_guarded_values_and_reject_invalid_hosts() -> eyre::Result<()> {
assert_chart_cases(
"nack",
vec![
SemanticCase::accepted(
"deleted legacy pull policy",
json!({ "jetstream": { "pullPolicy": null } }),
),
SemanticCase::accepted(
"non-string legacy pull policy bypasses the fixup",
json!({ "jetstream": { "pullPolicy": 7 } }),
),
SemanticCase::accepted(
"string legacy pull policy is moved then unset",
json!({ "jetstream": { "pullPolicy": "Always" } }),
),
SemanticCase::rejected("deleted helper host", "", json!({ "jetstream": null })),
SemanticCase::rejected(
"scalar helper host",
"/jetstream",
json!({ "jetstream": 7 }),
),
],
)?;
assert_chart_cases(
"nats-kafka",
vec![
SemanticCase::accepted(
"deleted tag override",
json!({ "image": { "tagOverride": null } }),
),
SemanticCase::accepted(
"numeric tag override is rendered by printf",
json!({ "image": { "tagOverride": 7 } }),
),
SemanticCase::accepted(
"string tag override is moved then unset",
json!({ "image": { "tagOverride": "changed" } }),
),
SemanticCase::rejected("deleted helper host", "", json!({ "image": null })),
SemanticCase::rejected("scalar helper host", "/image", json!({ "image": 7 })),
],
)
}
#[test]
fn cilium_inclusive_comparator_chains_bound_integer_domains() -> eyre::Result<()> {
assert_chart_cases(
"cilium",
vec![
SemanticCase::rejected(
"negative envoy baseID",
"/envoy/baseID",
json!({ "envoy": { "baseID": -5 } }),
),
SemanticCase::rejected(
"envoy baseID above 4294967295",
"/envoy/baseID",
json!({ "envoy": { "baseID": 4_294_967_296_i64 } }),
),
SemanticCase::accepted(
"envoy baseID inside the window",
json!({ "envoy": { "baseID": 42 } }),
),
SemanticCase::rejected(
"cluster id in the 128-255 window under ENI",
"",
json!({ "cluster": { "id": 200, "name": "c1" }, "eni": { "enabled": true } }),
),
SemanticCase::rejected(
"cluster id in the 384-511 window under AlibabaCloud",
"",
json!({ "cluster": { "id": 450, "name": "c1" }, "alibabacloud": { "enabled": true } }),
),
SemanticCase::accepted(
"cluster id below the window under ENI",
json!({ "cluster": { "id": 127, "name": "c1" }, "eni": { "enabled": true } }),
),
SemanticCase::accepted(
"affected cluster id without an affected datapath",
json!({ "cluster": { "id": 200, "name": "c1" } }),
),
SemanticCase::accepted(
"affected cluster id with the unsafe-skb opt-out",
json!({
"cluster": { "id": 200, "name": "c1" },
"eni": { "enabled": true },
"extraConfig": { "allow-unsafe-policy-skb-usage": "true" }
}),
),
],
)
}
#[test]
fn cilium_provider_modes_pin_routing_and_traffic_policy_domains() -> eyre::Result<()> {
assert_chart_cases(
"cilium",
vec![
SemanticCase::rejected(
"gke with tunnel routing aborts",
"",
json!({ "gke": { "enabled": true }, "routingMode": "tunnel",
"ipam": { "mode": "kubernetes" } }),
),
SemanticCase::accepted(
"gke with native routing renders",
json!({ "gke": { "enabled": true }, "routingMode": "native",
"ipam": { "mode": "kubernetes" },
"ipv4NativeRoutingCIDR": "10.0.0.0/8" }),
),
SemanticCase::rejected(
"aks-byocni with native routing aborts",
"",
json!({ "aksbyocni": { "enabled": true }, "routingMode": "native",
"ipv4NativeRoutingCIDR": "10.0.0.0/8" }),
),
SemanticCase::accepted(
"aks-byocni with tunnel routing renders",
json!({ "aksbyocni": { "enabled": true }, "routingMode": "tunnel" }),
),
SemanticCase::rejected(
"an unlisted ingress traffic policy aborts",
"/ingressController",
json!({ "ingressController": { "enabled": true, "service": {
"type": "LoadBalancer", "externalTrafficPolicy": "Foo" } } }),
),
SemanticCase::accepted(
"the Local ingress traffic policy renders",
json!({ "ingressController": { "enabled": true, "service": {
"type": "LoadBalancer", "externalTrafficPolicy": "Local" } } }),
),
SemanticCase::rejected(
"an unlisted gateway traffic policy aborts",
"/gatewayAPI",
json!({ "gatewayAPI": { "enabled": true,
"externalTrafficPolicy": "Foo" } }),
),
SemanticCase::accepted(
"the Cluster gateway traffic policy renders",
json!({ "gatewayAPI": { "enabled": true,
"externalTrafficPolicy": "Cluster" } }),
),
],
)
}
#[test]
fn airflow_minimum_version_terminal_rejects_older_semver() -> eyre::Result<()> {
assert_chart_cases(
"airflow",
vec![
SemanticCase::rejected(
"below the minimum supported version",
"",
json!({ "airflowVersion": "2.10.0" }),
),
SemanticCase::accepted(
"at the minimum supported version",
json!({ "airflowVersion": "2.11.0" }),
),
],
)
}
#[test]
fn jenkins_controller_replicas_domain_is_bounded() -> eyre::Result<()> {
assert_chart_cases(
"jenkins",
vec![
SemanticCase::rejected(
"replicas above the domain",
"/controller/replicas",
json!({ "controller": { "replicas": 2 } }),
),
SemanticCase::rejected(
"negative replicas below the domain",
"/controller/replicas",
json!({ "controller": { "replicas": -1 } }),
),
SemanticCase::accepted(
"the scale-down replica count",
json!({ "controller": { "replicas": 0 } }),
),
SemanticCase::accepted(
"the single supported replica",
json!({ "controller": { "replicas": 1 } }),
),
SemanticCase::rejected(
"a clean decimal spelling coerces into the failing domain",
"/controller/replicas",
json!({ "controller": { "replicas": "5" } }),
),
SemanticCase::accepted(
"a decimal spelling inside the domain",
json!({ "controller": { "replicas": "1" } }),
),
],
)
}
#[test]
fn airflow_scheduler_kind_partition_scopes_strategy_providers() -> eyre::Result<()> {
assert_chart_cases(
"airflow",
vec![
SemanticCase::rejected(
"numeric strategy in the live Deployment arm",
"/scheduler/strategy",
json!({ "scheduler": { "strategy": 7 } }),
),
SemanticCase::rejected(
"a StatefulSet-only member at the Deployment strategy slot",
"/scheduler/strategy",
json!({ "scheduler": { "strategy": { "rollingUpdate": { "partition": 1 } } } }),
),
SemanticCase::accepted(
"a Deployment strategy in the live arm",
json!({ "scheduler": { "strategy": { "type": "RollingUpdate" } } }),
),
SemanticCase::accepted(
"numeric strategy is harmless while the Deployment arm is dead",
json!({ "executor": "LocalExecutor", "scheduler": { "strategy": 7 } }),
),
SemanticCase::rejected(
"numeric updateStrategy in the live StatefulSet arm",
"/scheduler/updateStrategy",
json!({ "executor": "LocalExecutor", "scheduler": { "updateStrategy": 7 } }),
),
SemanticCase::accepted(
"a StatefulSet updateStrategy in the live arm",
json!({
"executor": "LocalExecutor",
"scheduler": { "updateStrategy": { "rollingUpdate": { "partition": 1 } } },
}),
),
],
)
}
#[test]
fn nats_extra_resources_items_must_be_objects() -> eyre::Result<()> {
assert_chart_cases(
"nats",
vec![
SemanticCase::rejected(
"Boolean item cannot decode as a resource",
"/extraResources/0",
json!({ "extraResources": [true] }),
),
SemanticCase::rejected(
"list item cannot decode as a resource",
"/extraResources/0",
json!({ "extraResources": [["audit"]] }),
),
SemanticCase::accepted(
"object item stays open",
json!({ "extraResources": [{ "apiVersion": "v1", "kind": "ConfigMap",
"metadata": { "name": "extra" } }] }),
),
SemanticCase::accepted(
"wrapper item is an object",
json!({ "extraResources": [{ "$tplYaml": indoc! {"
apiVersion: v1
kind: ConfigMap
metadata:
name: extra"} }] }),
),
],
)
}
#[test]
fn bitnami_postgresql_disabled_common_tag_loses_live_helpers() -> eyre::Result<()> {
assert_chart_cases(
"bitnami-postgresql",
vec![
SemanticCase::rejected(
"explicitly disabled library tag",
"/tags",
json!({ "tags": { "bitnami-common": false } }),
),
SemanticCase::accepted(
"explicitly enabled library tag",
json!({ "tags": { "bitnami-common": true } }),
),
SemanticCase::accepted("absent tag defaults to enabled", json!({ "tags": {} })),
],
)
}
#[test]
fn airflow_disabled_common_tag_is_scoped_to_the_postgresql_child() -> eyre::Result<()> {
assert_chart_cases(
"airflow",
vec![
SemanticCase::rejected(
"disabled tag under the default active child",
"",
json!({ "tags": { "bitnami-common": false } }),
),
SemanticCase::accepted(
"disabled tag with the child disabled",
json!({ "tags": { "bitnami-common": false }, "postgresql": { "enabled": false } }),
),
],
)
}
#[test]
fn airflow_checksum_annotations_do_not_string_type_root_labels() -> eyre::Result<()> {
assert_chart_cases(
"airflow",
vec![
SemanticCase::accepted(
"map-shaped root labels render through every template",
json!({ "labels": { "team": "data" } }),
),
SemanticCase::rejected(
"truthy scalar root labels terminate the mustMerge sites",
"/labels",
json!({ "labels": "oops" }),
),
],
)
}
#[test]
fn airflow_falsy_root_labels_render_while_live_merge_gates_bind() -> eyre::Result<()> {
assert_chart_cases(
"airflow",
vec![
SemanticCase::accepted(
"empty-string root labels leave every merge gate dead",
json!({ "labels": "" }),
),
SemanticCase::accepted(
"empty-list root labels leave every merge gate dead",
json!({ "labels": [] }),
),
SemanticCase::accepted(
"false root labels leave every merge gate dead",
json!({ "labels": false }),
),
SemanticCase::rejected(
"a truthy scheduler partner makes the merge gate live, so a \
falsy non-map root labels aborts mustMerge",
"/labels",
json!({
"labels": "",
"scheduler": { "labels": { "team": "data" } }
}),
),
],
)
}
#[test]
fn airflow_config_range_requires_only_iterable_sections() -> eyre::Result<()> {
assert_chart_cases(
"airflow",
vec![
SemanticCase::rejected(
"scalar config section",
"/config/traces",
json!({ "config": { "traces": 7 } }),
),
SemanticCase::accepted(
"array config section",
json!({ "config": { "traces": [] } }),
),
SemanticCase::accepted(
"deleted config section",
json!({ "config": { "traces": null } }),
),
SemanticCase::accepted(
"array config value",
json!({ "config": { "traces": { "otel_on": [] } } }),
),
SemanticCase::accepted(
"object config value",
json!({ "config": { "traces": { "otel_on": { "probe-member": {} } } } }),
),
SemanticCase::rejected(
"deleted templated bool dependency",
"",
json!({ "multiNamespaceMode": null }),
),
],
)
}
#[test]
fn airflow_worker_set_overrides_bind_strict_member_kinds() -> eyre::Result<()> {
assert_chart_cases(
"airflow",
vec![
SemanticCase::rejected(
"scalar labels in a worker set terminates mustMerge",
"/workers/celery/sets",
json!({ "workers": { "celery": { "sets": [
{ "name": "heavy", "labels": "oops" }
] } } }),
),
SemanticCase::rejected(
"scalar persistence in a worker set terminates the merge recursion",
"/workers/celery/sets",
json!({ "workers": { "celery": { "sets": [
{ "name": "heavy", "persistence": "oops" }
] } } }),
),
SemanticCase::accepted(
"map labels override renders",
json!({ "workers": { "celery": { "sets": [
{ "name": "heavy", "labels": { "tier": "heavy" } }
] } } }),
),
SemanticCase::accepted(
"structured per-set overrides render",
json!({ "workers": { "celery": { "sets": [
{ "name": "highcpu", "replicas": 2, "queue": "highcpu",
"persistence": { "enabled": true },
"resources": { "requests": { "cpu": "1" } } }
] } } }),
),
SemanticCase::accepted(
"an empty worker-set overlay inherits the provider leaves",
json!({ "workers": { "celery": { "sets": [{}] } } }),
),
SemanticCase::accepted(
"an open worker-set overlay inherits the provider leaves",
json!({ "workers": { "celery": { "sets": [
{ "extension": {} }
] } } }),
),
],
)
}
#[test]
fn airflow_rerooted_worker_lanes_bind_layered_provider_payloads() -> eyre::Result<()> {
assert_chart_cases(
"airflow",
vec![
SemanticCase::rejected(
"base-layer string runAsUser reaches the rendered pod context",
"/workers/securityContexts/pod/runAsUser",
json!({ "workers": { "securityContexts": { "pod": { "runAsUser": "oops" } } } }),
),
SemanticCase::accepted(
"base-layer integer runAsUser",
json!({ "workers": { "securityContexts": { "pod": { "runAsUser": 50000 } } } }),
),
SemanticCase::rejected(
"celery-layer string runAsUser reaches the rendered pod context",
"/workers/celery/securityContexts/pod/runAsUser",
json!({ "workers": { "celery": { "securityContexts": { "pod": { "runAsUser": "oops" } } } } }),
),
SemanticCase::accepted(
"the scrub drops null celery members before the sink",
json!({ "workers": { "celery": { "securityContexts": { "pod": { "runAsUser": null } } } } }),
),
SemanticCase::accepted(
"the celery layer shadows the base member",
json!({ "workers": {
"securityContexts": { "pod": { "runAsUser": "oops" } },
"celery": { "securityContexts": { "pod": { "runAsUser": 50000 } } },
} }),
),
SemanticCase::rejected(
"a live worker PDB keeps the provider field type",
"/workers/podDisruptionBudget/config/maxUnavailable",
json!({ "workers": {
"podDisruptionBudget": {
"enabled": true,
"config": { "maxUnavailable": { "bad": 7 } },
},
} }),
),
SemanticCase::accepted(
"a valid worker PDB payload renders",
json!({ "workers": {
"podDisruptionBudget": {
"enabled": true,
"config": { "maxUnavailable": "50%" },
},
} }),
),
SemanticCase::rejected(
"a live worker HPA keeps the provider item shape",
"/workers/hpa/metrics/0",
json!({ "workers": {
"hpa": {
"enabled": true,
"metrics": [{ "bogus": 7 }],
},
} }),
),
SemanticCase::accepted(
"a valid worker HPA metric renders",
json!({ "workers": {
"hpa": {
"enabled": true,
"metrics": [{
"type": "Resource",
"resource": {
"name": "cpu",
"target": {
"type": "Utilization",
"averageUtilization": 80,
},
},
}],
},
} }),
),
SemanticCase::accepted(
"a disabled worker HPA ignores its max replica spelling",
json!({ "workers": {
"hpa": {
"enabled": false,
"maxReplicaCount": true,
},
} }),
),
],
)
}
#[test]
fn vault_mode_dispatch_binds_httproute_and_redundancy_zone_fails() -> eyre::Result<()> {
assert_chart_cases(
"vault",
vec![
SemanticCase::rejected(
"an enabled httproute without parentRefs aborts",
"",
json!({ "server": { "httproute": { "enabled": true } } }),
),
SemanticCase::accepted(
"an enabled httproute with parentRefs renders",
json!({ "server": { "httproute": { "enabled": true,
"parentRefs": [ { "name": "gw" } ] } } }),
),
SemanticCase::accepted(
"external mode keeps the httproute gate dormant",
json!({ "injector": { "externalVaultAddr": "https://vault.example.com" },
"server": { "httproute": { "enabled": true } } }),
),
SemanticCase::rejected(
"redundancy zones without ha mode abort",
"",
json!({ "server": { "ha": { "raft": {
"redundancyZones": { "enabled": true } } } } }),
),
SemanticCase::rejected(
"redundancy zones with ha but without raft abort",
"",
json!({ "server": { "ha": { "enabled": true, "raft": {
"redundancyZones": { "enabled": true } } } } }),
),
SemanticCase::rejected(
"the full combination still aborts below the required cluster version",
"",
json!({ "server": { "ha": { "enabled": true, "raft": {
"enabled": true,
"config": "storage \"raft\" {\n autopilot_redundancy_zone = \"VAULT_REDUNDANCY_ZONE\"\n}\n",
"redundancyZones": { "enabled": true } } } } }),
),
SemanticCase::accepted(
"external mode keeps the redundancy-zone gates dormant",
json!({ "injector": { "externalVaultAddr": "https://vault.example.com" },
"server": { "ha": { "raft": {
"redundancyZones": { "enabled": true } } } } }),
),
],
)
}
#[test]
fn kube_prometheus_stack_dashboard_gates_decode_the_version_policy() -> eyre::Result<()> {
assert_chart_cases(
"kube-prometheus-stack",
vec![
SemanticCase::rejected(
"the operator dashboards without matchLabels abort",
"",
json!({ "grafana": { "operator": {
"dashboardsConfigMapRefEnabled": true } } }),
),
SemanticCase::accepted(
"operator dashboards with matchLabels render",
json!({ "grafana": { "operator": {
"dashboardsConfigMapRefEnabled": true,
"matchLabels": { "app": "grafana" } } } }),
),
SemanticCase::accepted(
"a pre-1.14 version override keeps the dashboards dormant",
json!({ "grafana": { "operator": {
"dashboardsConfigMapRefEnabled": true } },
"kubeTargetVersionOverride": "1.13.0" }),
),
],
)
}
#[test]
fn kube_prometheus_stack_dig_subjects_bind_the_even_null_contract() -> eyre::Result<()> {
assert_chart_cases(
"kube-prometheus-stack",
vec![
SemanticCase::rejected(
"a scalar customRules aborts",
"/customRules",
json!({ "customRules": "junk" }),
),
SemanticCase::rejected(
"scalar additionalRuleAnnotations abort",
"/defaultRules/additionalRuleAnnotations",
json!({ "defaultRules": { "additionalRuleAnnotations": "junk" } }),
),
SemanticCase::accepted(
"map-shaped custom rules render",
json!({ "customRules": {
"KubeStateMetricsListErrors": { "severity": "warning" } } }),
),
SemanticCase::accepted(
"disabling defaultRules keeps every spelling dormant",
json!({ "customRules": "junk",
"defaultRules": { "create": false } }),
),
],
)
}
#[test]
fn metrics_server_navigated_hosts_reject_null_deletion() -> eyre::Result<()> {
assert_chart_cases(
"metrics-server",
vec![
SemanticCase::rejected(
"a deleted apiService host aborts the create read",
"",
json!({ "apiService": null }),
),
SemanticCase::rejected(
"a deleted addonResizer host aborts the enabled read",
"",
json!({ "addonResizer": null }),
),
SemanticCase::rejected(
"a deleted metrics host aborts the deployment read",
"",
json!({ "metrics": null }),
),
SemanticCase::accepted(
"an empty apiService map reads create as nil",
json!({ "apiService": {} }),
),
SemanticCase::accepted(
"an explicit apiService map renders",
json!({ "apiService": { "create": false } }),
),
],
)
}
#[test]
fn reloader_service_account_host_rejects_present_scalars() -> eyre::Result<()> {
assert_chart_cases(
"reloader",
vec![
SemanticCase::rejected(
"a scalar serviceAccount aborts the create read",
"/reloader/serviceAccount",
json!({ "reloader": { "serviceAccount": 7 } }),
),
SemanticCase::rejected(
"a string serviceAccount aborts the same read",
"/reloader/serviceAccount",
json!({ "reloader": { "serviceAccount": "default" } }),
),
SemanticCase::accepted(
"an object serviceAccount renders",
json!({ "reloader": { "serviceAccount": { "create": false } } }),
),
],
)
}
#[test]
fn prometheus_dependency_values_roots_must_be_tables() -> eyre::Result<()> {
assert_chart_cases(
"prometheus",
vec![
SemanticCase::rejected(
"a scalar dependency root aborts dependency processing",
"/prometheus-pushgateway",
json!({ "prometheus-pushgateway": 7 }),
),
SemanticCase::rejected(
"a list dependency root aborts the same way",
"/prometheus-pushgateway",
json!({ "prometheus-pushgateway": ["enabled"] }),
),
SemanticCase::accepted(
"an empty dependency root table renders",
json!({ "prometheus-pushgateway": {} }),
),
SemanticCase::accepted(
"a deleted dependency root falls back to subchart defaults",
json!({ "prometheus-pushgateway": null }),
),
],
)
}
#[test]
fn ranged_document_members_must_decode_as_manifests() -> eyre::Result<()> {
for (chart, path, rejected_path) in [
("traefik", "extraObjects", "/extraObjects"),
("sealed-secrets", "extraDeploy", "/extraDeploy"),
("grafana", "extraObjects", "/extraObjects"),
] {
assert_chart_cases(
chart,
vec![
SemanticCase::rejected(
"an integer member cannot decode as a document",
rejected_path,
json!({ path: [7] }),
),
SemanticCase::rejected(
"a sequence member decodes as a list, not a manifest",
rejected_path,
json!({ path: [["kind: ConfigMap"]] }),
),
SemanticCase::accepted(
"a raw manifest string renders through the string arm",
json!({ path: ["apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: extra\n"] }),
),
SemanticCase::accepted(
"a mapping member renders through the serialized arm",
json!({ path: [{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": { "name": "extra" },
}] }),
),
SemanticCase::accepted(
"a null member renders an empty document",
json!({ path: [null] }),
),
],
)?;
}
Ok(())
}
#[test]
fn unquoted_slots_reject_token_breaking_sources() -> eyre::Result<()> {
assert_chart_cases(
"crossplane",
vec![
SemanticCase::rejected(
"a ranged env key holding `: ` breaks the name slot",
"/extraEnvVarsCrossplane",
json!({ "extraEnvVarsCrossplane": { "BAD: KEY": "x" } }),
),
SemanticCase::accepted(
"a dotted key renders (the replace only rewrites `.`)",
json!({ "extraEnvVarsCrossplane": { "GOOD.KEY": "x" } }),
),
],
)?;
assert_chart_cases(
"external-dns",
vec![
SemanticCase::rejected(
"a data key holding `: ` opens a nested mapping",
"/secretConfiguration/data",
json!({ "secretConfiguration": {
"enabled": true,
"mountPath": "/etc/secrets",
"data": { "BAD: KEY": "x" },
} }),
),
SemanticCase::rejected(
"a mountPath holding `: ` breaks the volumeMount slot",
"/secretConfiguration/mountPath",
json!({ "secretConfiguration": {
"enabled": true,
"mountPath": "/etc/a: b",
"data": { "K": "v" },
} }),
),
SemanticCase::accepted(
"safe keys and a safe mountPath render",
json!({ "secretConfiguration": {
"enabled": true,
"mountPath": "/etc/secrets",
"data": { "GOOD_KEY": "x" },
} }),
),
SemanticCase::accepted(
"a templated mountPath renders whatever the program produces",
json!({ "secretConfiguration": {
"enabled": true,
"mountPath": "/etc/{{ .Release.Name }}",
"data": { "GOOD_KEY": "x" },
} }),
),
],
)?;
assert_chart_cases(
"cluster-autoscaler",
vec![
SemanticCase::rejected(
"a magnum cluster name holding `: ` renders a mapping command item",
"/magnumClusterName",
json!({
"cloudProvider": "magnum",
"magnumClusterName": "x: y",
"autoDiscovery": { "namespace": "default" },
}),
),
SemanticCase::accepted(
"a plain magnum cluster name renders a string command item",
json!({
"cloudProvider": "magnum",
"magnumClusterName": "prod",
"autoDiscovery": { "namespace": "default" },
}),
),
],
)
}
#[test]
fn helper_body_slots_bind_the_document_their_caller_renders() -> eyre::Result<()> {
assert_chart_cases(
"promtail",
vec![
SemanticCase::rejected(
"a ranged container key holding `: ` breaks the name slot",
"/extraContainers",
json!({ "extraContainers": { "a: b": { "image": "nginx" } } }),
),
SemanticCase::accepted(
"a plain container key renders",
json!({ "extraContainers": { "proxy": { "image": "nginx" } } }),
),
],
)?;
assert_chart_cases(
"grafana",
vec![
SemanticCase::rejected(
"a templated envFrom secret holding `: ` breaks the envFrom slot",
"/envFromSecret",
json!({ "envFromSecret": "a: b" }),
),
SemanticCase::accepted(
"a plain envFrom secret renders",
json!({ "envFromSecret": "grafana-env" }),
),
],
)?;
assert_chart_cases(
"jenkins",
vec![
SemanticCase::rejected(
"a JCasC annotation key holding `: ` breaks the embedded document",
"/agent/annotations",
json!({ "agent": { "annotations": { "a: b": "x" } } }),
),
SemanticCase::accepted(
"a plain annotation key renders",
json!({ "agent": { "annotations": { "example.com/scrape": "x" } } }),
),
],
)
}
#[test]
fn serialized_and_helper_splices_keep_their_provider_shapes() -> eyre::Result<()> {
assert_chart_cases(
"reloader",
vec![
SemanticCase::rejected(
"a container-item resources map keeps ResourceRequirements typing",
"/reloader/deployment/resources",
json!({ "reloader": { "deployment": { "resources": { "bogus": 7 } } } }),
),
SemanticCase::rejected(
"a container-item volumeMounts list keeps VolumeMount typing",
"/reloader/deployment/volumeMounts",
json!({ "reloader": { "deployment": { "volumeMounts": [{ "bogus": 7 }] } } }),
),
SemanticCase::rejected(
"a set-mutated containerSecurityContext keeps SecurityContext typing",
"/reloader/deployment/containerSecurityContext",
json!({ "reloader": { "deployment": { "containerSecurityContext": { "bogus": 7 } } } }),
),
SemanticCase::accepted(
"the real container shapes still render",
json!({ "reloader": { "deployment": {
"resources": { "limits": { "cpu": "100m" } },
"volumeMounts": [{ "name": "tmp", "mountPath": "/tmp" }],
"containerSecurityContext": { "runAsNonRoot": true },
} } }),
),
],
)?;
assert_chart_cases(
"zalando-postgres-operator",
vec![
SemanticCase::rejected(
"a populated declared default does not re-open the sink",
"/resources",
json!({ "resources": { "bogus": 7 } }),
),
SemanticCase::accepted(
"the chart's own declared resources still validate",
json!({ "resources": { "limits": { "cpu": "1" }, "requests": { "cpu": "1" } } }),
),
],
)?;
assert_chart_cases(
"sealed-secrets",
vec![
SemanticCase::rejected(
"an omitted `enabled` flag does not open the Probe payload",
"/livenessProbe",
json!({ "livenessProbe": { "enabled": true, "bogus": 7 } }),
),
SemanticCase::rejected(
"an omitted `enabled` flag does not open the SecurityContext payload",
"/containerSecurityContext",
json!({ "containerSecurityContext": { "enabled": true, "bogus": 7 } }),
),
SemanticCase::accepted(
"the chart's own enabled-flagged probes and contexts still render",
json!({
"livenessProbe": { "enabled": true, "periodSeconds": 20 },
"containerSecurityContext": { "enabled": true, "runAsNonRoot": true },
}),
),
],
)
}
#[test]
fn own_line_splices_reach_the_slot_their_column_names() -> eyre::Result<()> {
assert_chart_cases(
"vault",
vec![
SemanticCase::rejected(
"the injector strategy lands in DeploymentStrategy, not the selector",
"/injector/strategy",
json!({ "injector": { "strategy": { "bogus": 7 } } }),
),
SemanticCase::rejected(
"a scalar strategy is not a DeploymentStrategy either",
"/injector/strategy",
json!({ "injector": { "strategy": 7 } }),
),
SemanticCase::accepted(
"the documented rolling-update strategy still renders",
json!({ "injector": { "strategy": {
"type": "RollingUpdate",
"rollingUpdate": { "maxSurge": 1, "maxUnavailable": 1 },
} } }),
),
],
)?;
assert_chart_cases(
"jenkins",
vec![
SemanticCase::rejected(
"the pod spec survives a multiline template comment",
"/persistence/volumes",
json!({ "persistence": { "volumes": [{ "bogus": 7 }] } }),
),
SemanticCase::accepted(
"real pod volumes still render",
json!({ "persistence": { "volumes": [{ "name": "extra", "emptyDir": {} }] } }),
),
],
)
}
#[test]
fn member_and_root_presence_claims_reach_their_hosts() -> eyre::Result<()> {
assert_chart_cases(
"minio",
vec![
SemanticCase::rejected(
"a secret-backed user must name the secret key it reads",
"/users",
json!({ "users": [{ "accessKey": "a", "secretKey": "b",
"policy": "readonly", "existingSecret": "sec" }] }),
),
SemanticCase::rejected(
"the same holds for service accounts",
"/svcaccts",
json!({ "svcaccts": [{ "accessKey": "a", "secretKey": "b",
"user": "console", "existingSecret": "sec" }] }),
),
SemanticCase::accepted(
"naming the key renders",
json!({ "users": [{ "accessKey": "a", "secretKey": "b", "policy": "readonly",
"existingSecret": "sec", "existingSecretKey": "k" }] }),
),
SemanticCase::accepted(
"a user with no secret reference keeps the gate dormant",
json!({ "users": [{ "accessKey": "a", "secretKey": "b",
"policy": "readonly" }] }),
),
],
)?;
assert_chart_cases(
"kube-prometheus-stack",
vec![
SemanticCase::rejected(
"deleting the dug custom rules aborts the type assertion",
"",
json!({ "customRules": null }),
),
SemanticCase::accepted(
"a populated rule override renders",
json!({ "customRules": { "PrometheusBadConfig": { "for": "5m" } } }),
),
],
)
}
#[test]
fn dependency_owned_deletions_bind_under_a_surviving_root() -> eyre::Result<()> {
assert_chart_cases(
"kube-prometheus-stack",
vec![
SemanticCase::rejected(
"a deleted dependency-owned host aborts the member read",
"",
json!({ "grafana": { "sidecar": { "dashboards": null } } }),
),
SemanticCase::rejected(
"a deleted parent-only host under the dependency root aborts",
"/grafana",
json!({ "grafana": { "operator": null } }),
),
SemanticCase::rejected(
"the refilled root drops the parent's own keys for it",
"",
json!({ "grafana": null }),
),
SemanticCase::accepted(
"turning the sidecar off keeps the host present",
json!({ "grafana": { "sidecar": { "dashboards": { "enabled": false } } } }),
),
],
)?;
assert_chart_cases(
"kyverno",
vec![
SemanticCase::accepted(
"a deleted root whose every read is subchart-declared refills",
json!({ "grafana": null }),
),
SemanticCase::rejected(
"a deletion inside that same root still aborts",
"/crds",
json!({ "crds": { "migration": null } }),
),
],
)?;
assert_chart_cases(
"signoz-signoz",
vec![SemanticCase::rejected(
"a nested subchart deletion aborts its own read",
"/clickhouse",
json!({ "clickhouse": { "image": null } }),
)],
)
}
#[test]
fn quoted_tpl_inputs_keep_their_placement_language() -> eyre::Result<()> {
assert_chart_cases(
"karpenter",
vec![
SemanticCase::rejected(
"an action-free cluster name cannot close its quoted env value",
"/settings/clusterName",
json!({ "settings": { "clusterName": "bad\"name" } }),
),
SemanticCase::rejected(
"toString retains the recursive quote preimage",
"/settings/clusterEndpoint",
json!({
"settings": {
"clusterName": "audit",
"clusterEndpoint": { "host": "bad\"endpoint" },
}
}),
),
SemanticCase::accepted(
"a template program has an independently rendered result",
json!({
"settings": {
"clusterName": r#"{{ "audit" }}"#,
"clusterEndpoint": r#"{{ "https://example.com" }}"#,
}
}),
),
SemanticCase::accepted(
"safe action-free values still render",
json!({
"settings": {
"clusterName": "audit",
"clusterEndpoint": "https://example.com",
}
}),
),
],
)?;
assert_chart_cases(
"jenkins",
vec![
SemanticCase::rejected(
"an enabled PVC cannot embed an unescaped quote",
"/persistence/storageClass",
json!({ "persistence": { "enabled": true, "storageClass": "bad\"class" } }),
),
SemanticCase::accepted(
"the PVC-disabled branch does not render the storage class",
json!({ "persistence": { "enabled": false, "storageClass": "bad\"class" } }),
),
SemanticCase::accepted(
"a safe storage class still renders",
json!({ "persistence": { "enabled": true, "storageClass": "standard" } }),
),
SemanticCase::accepted(
"a storage-class program has an independently rendered result",
json!({
"persistence": {
"enabled": true,
"storageClass": r#"{{ "standard" }}"#,
}
}),
),
],
)
}
#[test]
fn chart_authored_tpl_programs_keep_their_selected_dependencies() -> eyre::Result<()> {
assert_chart_cases(
"grafana",
vec![
SemanticCase::rejected(
"deleting the selected route root aborts the default program",
"",
json!({ "route": null }),
),
SemanticCase::accepted(
"the declared disabled route keeps the fallback branch valid",
json!({ "route": { "main": { "enabled": false } } }),
),
SemanticCase::accepted(
"an override replaces the chart-authored program",
json!({
"grafana.ini": { "server": { "domain": "fixed.example.com" } },
"route": null,
}),
),
SemanticCase::accepted(
"the ingress branch short-circuits the route dependency",
json!({
"ingress": {
"enabled": true,
"hosts": ["grafana.example.com"],
},
"route": null,
}),
),
],
)
}