Files
cozystack/hack/cozystack-version-stamp.bats
Myasnikov Daniil cc942fed68 test(tests): run unit suites with Bats and retain POSIX coverage
The shell translator rewrites test bodies and loses Bats reporting.
The unit lane needs complete TAP results and JUnit reports without
changing how live-cluster suites run.

Sourced production helpers still need a dash pass, and unit test bodies
need nounset without exporting shell options into subprocesses.

Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-10-07 11:04:00 +03:00

147 lines
6.2 KiB
Bash

#!/usr/bin/env bats
# -----------------------------------------------------------------------------
# Unit tests for
# packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh
#
# render_cozystack_version_manifest <version> emits the cozystack-version
# ConfigMap manifest on stdout, always carrying the
# platform.cozystack.io/no-delete=true label that the
# cozystack-no-delete-guardrail ValidatingAdmissionPolicy keys on. Migrations
# apply it under the default kubectl field manager; a label-less apply by that
# same manager would strip the label, so the manifest is centralized here and
# every migration sources it instead of copy-pasting a heredoc.
#
# CI runs this file under Bats through `make bats-unit-tests`. It also remains
# compatible with the legacy `hack/cozytest.sh` translator, whose awk parser
# recognizes only @test blocks and a bare `}` on its own line. The tests avoid
# `run` and `$status` for that compatibility path and use direct shell
# assertions. Each test runs in its own process, so NAMESPACE does not leak.
#
# Run with: bats hack/cozystack-version-stamp.bats
# -----------------------------------------------------------------------------
load test_helper
@test "renders parseable YAML for a version" {
. packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh
render_cozystack_version_manifest 45 | yq . >/dev/null
}
@test "carries the no-delete label set to true" {
. packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh
val=$(render_cozystack_version_manifest 45 \
| yq -r '.metadata.labels."platform.cozystack.io/no-delete"')
[ "$val" = "true" ]
}
@test "names the ConfigMap cozystack-version" {
. packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh
name=$(render_cozystack_version_manifest 45 | yq -r '.metadata.name')
[ "$name" = "cozystack-version" ]
}
@test "stamps the requested version as a quoted string" {
# The version MUST render as a quoted YAML string; an unquoted numeric
# scalar (version: 45) would change the ConfigMap data type the rest of
# the platform reads as a string.
. packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh
out=$(render_cozystack_version_manifest 45)
printf '%s\n' "$out" | grep -qx ' version: "45"'
v=$(printf '%s\n' "$out" | yq -r '.data.version')
[ "$v" = "45" ]
}
@test "defaults namespace to cozy-system when NAMESPACE is unset" {
unset NAMESPACE
. packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh
ns=$(render_cozystack_version_manifest 45 | yq -r '.metadata.namespace')
[ "$ns" = "cozy-system" ]
}
@test "honors a NAMESPACE override" {
. packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh
out=$(export NAMESPACE=cozy-other; render_cozystack_version_manifest 45)
ns=$(printf '%s\n' "$out" | yq -r '.metadata.namespace')
[ "$ns" = "cozy-other" ]
}
@test "requires a version argument" {
. packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh
if ( render_cozystack_version_manifest ) 2>/dev/null; then
echo "expected non-zero exit when version arg is missing" >&2
exit 1
fi
}
@test "stamp_cozystack_version requires a version argument" {
# The guard fires before the kubectl pipe, so this needs no cluster: a
# missing version must abort rather than apply empty input to kubectl.
. packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh
if ( stamp_cozystack_version ) 2>/dev/null; then
echo "expected non-zero exit when version arg is missing" >&2
exit 1
fi
}
@test "render output matches the canonical labeled manifest" {
# Golden test: pins the exact bytes migrations 42/43/44 apply today, so the
# refactor that routes them through this helper is provably manifest-stable.
unset NAMESPACE
. packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh
expected=$(cat <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: cozystack-version
namespace: cozy-system
labels:
platform.cozystack.io/no-delete: "true"
data:
version: "45"
EOF
)
actual=$(render_cozystack_version_manifest 45)
if [ "$actual" != "$expected" ]; then
printf 'expected:\n%s\n---\nactual:\n%s\n' "$expected" "$actual" >&2
exit 1
fi
}
@test "no go-forward stamp bypasses the helper (migrations >= 42 and run-migrations.sh)" {
# The helper exists so go-forward stamps cannot drift back to a label-less
# apply that drops the no-delete label. Migrations 1-41 are frozen and
# backfilled by 42, so they are exempt; migration 42 onward and the
# bootstrap path must each source the helper, call its API, and never stamp
# cozystack-version directly with kubectl. Blocking only the historical
# 'kubectl create configmap' shape is too weak — an inline 'kubectl apply'
# of a hand-rolled label-less manifest would slip past it.
mig_dir=packages/core/platform/images/migrations/migrations
files=""
for f in "$mig_dir"/*; do
n=$(basename "$f")
case "$n" in *[!0-9]*) continue ;; esac # skip lib/ and other non-numeric
[ "$n" -ge 42 ] || continue
files="$files $f"
done
files="$files packages/core/platform/images/migrations/run-migrations.sh"
for f in $files; do
if ! grep -Eq 'cozystack-version\.sh' "$f"; then
echo "$f does not source the cozystack-version helper" >&2
exit 1
fi
if ! grep -Eq '(render_cozystack_version_manifest|stamp_cozystack_version)' "$f"; then
echo "$f does not call the cozystack-version helper API" >&2
exit 1
fi
# Strip full-comment lines first: a comment that explains why a stamp
# routes through the helper must not read as a direct stamp itself. A
# real bypass command never starts with '#', so the guard keeps its
# strength against actual direct stamping.
if grep -v '^[[:space:]]*#' "$f" | grep -Eq 'kubectl[[:space:]].*(create[[:space:]]+configmap|apply).*cozystack-version'; then
echo "$f stamps cozystack-version directly instead of via the helper" >&2
exit 1
fi
done
}