Files
cozystack/hack/check-values-schema-string-defaults.bats
Aleksei Sviridkin cf3cb0f133 test(values): reject a boolean default on a string-typed schema property
cozyvalues-gen quotes a default only when the declared type is the literal
`string` or `quantity`; a named alias, including one declared `@enum
{string}`, falls through to a YAML re-parse of the bare value where the 1.1
boolean words resolve. A chart spelling out a `"no"` default under such a
type emits `"default": false` into a string-typed, enum-constrained property
and `+kubebuilder:default:=false` onto the matching Go field. Templates read
values.yaml rather than the schema, so the chart suites stay green while the
shipped schema and CRD carry a default the field rejects.

Vendored schemas under packages/*/*/charts/ are left out of the scan:
cozyvalues-gen never writes them, they change only through `make update`,
and a violation there would have no fix inside this tree.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-09-02 17:58:05 +03:00

57 lines
3.0 KiB
Bash

#!/usr/bin/env bats
# Unit test: no generated values.schema.json may declare a non-string default
# on a property typed "string".
#
# cozyvalues-gen quotes a default only when the declared type is the literal
# `string` or `quantity` (formatDefault in internal/openapi/openapi.go). A
# named alias — including one declared `@enum {string}` — falls through to a
# YAML re-parse of the bare value, where the 1.1 boolean words resolve. So a
# chart that materialises `authClients: "no"` under an `@enum` type emits
# `"default": false` into a property whose type is "string" and whose enum is
# ["no","optional","yes"] — a default the field's own schema rejects. The same
# coercion reaches api/ as `+kubebuilder:default:=false` on a string-typed Go
# field, so the CRD defaults an enum-constrained string to a boolean and every
# CR relying on the default is rejected. "optional" survives; "no" and "yes"
# do not. The quotes in values.yaml do not help: the value reaches
# formatDefault as a flat string with no record of having been quoted.
#
# The damage is invisible to the chart test suites: templates read the value
# from values.yaml, not from the schema, so rendering stays correct and only
# the shipped schema and CRD are wrong. This test is the guard, and it is why
# packages/apps/redis/values.yaml leaves `tls: {}` empty and resolves the
# authClients default in templates/_tls.tpl instead.
#
# Runs under hack/cozytest.sh, which is POSIX /bin/sh — no process
# substitution, no arrays, no bats `run`.
REPO_ROOT="$(cd "$(dirname "${BATS_TEST_FILENAME:-$0}")/.." && pwd)"
@test "no values.schema.json declares a non-string default on a string-typed property" {
# Vendored charts under packages/*/*/charts/ are pulled by `make update`, not
# generated, so a violation there has no fix inside this tree.
# An empty result means "no violations" only if the scan actually reached
# some schemas. A renamed directory, a wrong root or a missing jq all yield
# the same empty string, and the test would pass having read nothing.
scanned="$(find "$REPO_ROOT/packages" -name values.schema.json -not -path '*/charts/*' | wc -l | tr -d ' ')"
if [ "$scanned" -eq 0 ]; then
echo "no values.schema.json found under $REPO_ROOT/packages -- guard is blind." >&2
return 1
fi
violations="$(find "$REPO_ROOT/packages" -name values.schema.json -not -path '*/charts/*' -exec jq -r '
input_filename as $f
| [ .. | objects
| select(.type == "string" and has("default") and (.default | type) != "string")
| "\($f): default=\(.default | tojson) enum=\(.enum // "none" | tojson)"
] | .[]' {} +)"
if [ -n "$violations" ]; then
echo "String-typed schema properties carrying a non-string default:" >&2
echo "$violations" >&2
echo "This is cozyvalues-gen coercing a YAML 1.1 boolean word (no/yes/on/off)" >&2
echo "out of values.yaml. Leave the field absent from values.yaml and resolve" >&2
echo "its default in the template instead." >&2
return 1
fi
}