Files
cozystack/hack/select-install_test.bats
Andrey Kolkov 43b02bda30 test(e2e): run the Redis backup round-trip against Valkey
The Redis backup demo now takes APP_KIND=Valkey, which switches the app
kind and the chart prefix the helpers derive sentinel and Secret names
from. The demo BackupClass routes Valkey to the same demo strategy.

The valkey suite gains valkey-backup-roundtrip, which drives that demo
end to end: seed a marker, back it up, restore it in place and into a
copy. It writes to tenant-root's seaweedfs through seaweedfs-s3, so a
seaweedfs change now selects the valkey suite too, and since the
round-trip runs examples/backups/redis as its harness, an edit there
selects both the redis and the valkey suites. The reduced E2E install
keeps the backup stack for the valkey suite, as it does for the other
backup round-trip suites.

Signed-off-by: Andrey Kolkov <androndo@gmail.com>
Assisted-By: LLM
2026-10-13 12:34:24 +04:00

751 lines
30 KiB
Bash

#!/usr/bin/env bats
# -----------------------------------------------------------------------------
# Unit tests for hack/select-install.sh
#
# 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`, `$status`, setup(), and teardown() for that compatibility path. Cleanup
# stays at the last reachable line rather than in an EXIT trap, which would
# replace Bats' bookkeeping trap and hide a failing TAP result.
#
# Tests that only READ the production sources call the script directly (it
# defaults sources-dir to packages/core/platform/sources); only the tests that
# build a synthetic graph or suite tree use a scratch dir.
#
# Run with: bats hack/select-install_test.bats
# -----------------------------------------------------------------------------
load test_helper
assert_contains_package() {
printf '%s\n' "$1" | tr ' ' '\n' | grep -Fxq "$2"
}
assert_output_graph_error() {
mode=$1
sources=$2
expected=$3
requested_suite=${4:-postgres}
out=$(mktemp)
err=$(mktemp)
if [ "$mode" = disabled ]; then
if hack/select-install.sh --disabled "$requested_suite" "$sources" >"$out" 2>"$err"; then
echo "expected disabled mode to reject invalid graph $sources" >&2
exit 1
fi
elif hack/select-install.sh "$requested_suite" "$sources" >"$out" 2>"$err"; then
echo "expected closure mode to reject invalid graph $sources" >&2
exit 1
fi
[ ! -s "$out" ]
grep -Fq "$expected" "$err"
rm -f "$out" "$err"
}
@test "single app selects its forward dependency closure" {
output=$(hack/select-install.sh "postgres")
assert_contains_package "$output" cozystack.postgres-application
assert_contains_package "$output" cozystack.postgres-operator
assert_contains_package "$output" cozystack.networking
# engine edge is KEPT on the install walk (unlike select-e2e.sh)
assert_contains_package "$output" cozystack.cozystack-engine
}
@test "closure is transitive (deps of deps are pulled in)" {
output=$(hack/select-install.sh "postgres")
# cert-manager is a 2-hop dep (via postgres-operator and via the engine)
assert_contains_package "$output" cozystack.cert-manager
# gateway-api-crds is a 3-hop dep (postgres-application -> networking -> gateway-api-crds)
assert_contains_package "$output" cozystack.gateway-api-crds
}
@test "app pulls its direct operator dependencies" {
output=$(hack/select-install.sh "harbor")
assert_contains_package "$output" cozystack.harbor-application
assert_contains_package "$output" cozystack.postgres-operator
assert_contains_package "$output" cozystack.redis-operator
assert_contains_package "$output" cozystack.objectstorage-controller
}
@test "kubernetes suites keep the cluster and worker pool APIs" {
for suite in kubernetes-latest kubernetes-previous; do
output=$(hack/select-install.sh "$suite")
assert_contains_package "$output" cozystack.kubernetes-application
assert_contains_package "$output" cozystack.kubernetes-nodes-application
done
}
@test "mongodb keeps the operator for its PerconaServerMongoDB resources" {
output=$(hack/select-install.sh mongodb)
assert_contains_package "$output" cozystack.mongodb-application
assert_contains_package "$output" cozystack.mongodb-operator
}
@test "root SeaweedFS keeps COSI for suites without backup steps" {
output=$(hack/select-install.sh kuberture)
assert_contains_package "$output" cozystack.seaweedfs-application
assert_contains_package "$output" cozystack.objectstorage-controller
# A backup suite would hide a lost SeaweedFS edge by retaining COSI itself.
tmp=$(mktemp -d)
cp -r packages/core/platform/sources "$tmp/sources"
yq -i '(.spec.variants[].dependsOn) -= ["cozystack.objectstorage-controller"]' \
"$tmp/sources/seaweedfs-application.yaml"
for mode in closure disabled; do
assert_output_graph_error "$mode" "$tmp/sources" \
"runtime baseline dependency closure is missing: cozystack.objectstorage-controller" kuberture
done
rm -rf "$tmp"
}
@test "vminstance keeps both application owners" {
output=$(hack/select-install.sh "vminstance")
printf '%s\n' "$output" | tr ' ' '\n' | grep -Fxq cozystack.vm-instance-application
printf '%s\n' "$output" | tr ' ' '\n' | grep -Fxq cozystack.vm-disk-application
printf '%s\n' "$output" | tr ' ' '\n' | grep -Fxq cozystack.kubevirt-cdi
}
@test "multiple suites union their closures" {
output=$(hack/select-install.sh "postgres kafka")
assert_contains_package "$output" cozystack.postgres-application
assert_contains_package "$output" cozystack.kafka-application
}
@test "suite list can be read from stdin with -" {
output=$(printf '%s\n' "postgres kafka" | hack/select-install.sh -)
assert_contains_package "$output" cozystack.postgres-application
assert_contains_package "$output" cozystack.kafka-application
assert_contains_package "$output" cozystack.cozystack-engine
}
@test "empty suites select nothing" {
output=$(hack/select-install.sh "")
[ -z "$output" ]
}
@test "unmapped suites are a hard error, all reported, no partial output" {
# Fail closed: a suite that maps to nothing must abort, not silently emit an
# empty set (which would install nothing and let the run pass vacuously).
# Also lock the contract that EVERY bad suite is reported in one run (not
# just the first) and that no partial closure leaks to stdout.
out=$(mktemp); err=$(mktemp)
if hack/select-install.sh "bad-one postgres bad-two" >"$out" 2>"$err"; then
echo "expected a non-zero exit for unmapped suites" >&2
exit 1
fi
grep -q "bad-one" "$err"
grep -q "bad-two" "$err"
[ ! -s "$out" ]
rm -f "$out" "$err"
}
@test "suite whose source omits the -application suffix resolves via fallback" {
# kuberture's PackageSource is cozystack.kuberture (no -application suffix)
output=$(hack/select-install.sh "kuberture")
assert_contains_package "$output" cozystack.kuberture
}
@test "securitygroup closure includes the engine that serves sdn.cozystack.io" {
# Regression for the under-selection bug: the securitygroup suite applies
# sdn.cozystack.io/v1alpha1, served by the aggregated apiserver
# (a cozystack-engine component). The forward walk from the controller can't
# reach the engine, so it must be seeded. Assert the controller AND the
# engine AND the engine's stable prerequisites — asserting only the
# controller (a single member) is what let the bug through.
output=$(hack/select-install.sh "securitygroup")
assert_contains_package "$output" cozystack.securitygroup-controller
assert_contains_package "$output" cozystack.cozystack-engine
assert_contains_package "$output" cozystack.cert-manager
assert_contains_package "$output" cozystack.networking
assert_contains_package "$output" cozystack.gateway-api-crds
}
@test "runtime baseline rejects a lost etcd operator dependency edge" {
# etcd-application is also a baseline seed, so merely selecting etcd cannot
# prove that its operator edge is necessary.
tmp=$(mktemp -d)
cp -r packages/core/platform/sources "$tmp/sources"
hack/select-install.sh postgres "$tmp/sources" >/dev/null
yq -i '(.spec.variants[].dependsOn) -= ["cozystack.etcd-operator"]' \
"$tmp/sources/etcd-application.yaml"
for mode in closure disabled; do
assert_output_graph_error "$mode" "$tmp/sources" \
"runtime baseline dependency closure is missing: cozystack.etcd-operator"
done
rm -rf "$tmp"
}
@test "backup suites keep the APIs used by their roundtrip examples" {
for suite in clickhouse etcd kafka kafka-metadata mariadb mongodb postgres rabbitmq redis valkey; do
keep=$(hack/select-install.sh "$suite")
drop=$(hack/select-install.sh --disabled "$suite")
for required in cozystack.backupstrategy-controller cozystack.backup-controller \
cozystack.bucket-application cozystack.objectstorage-controller cozystack.velero; do
assert_contains_package "$keep" "$required"
case " $drop " in
*" $required "*) echo "suite '$suite' disables backup prerequisite '$required'" >&2; exit 1 ;;
esac
done
done
# Suites without backup steps must not acquire the backup stack as baseline.
drop=$(hack/select-install.sh --disabled kuberture)
assert_contains_package "$drop" cozystack.backupstrategy-controller
}
@test "gateway keeps the pod admission webhook used by its churn test" {
output=$(hack/select-install.sh gateway)
assert_contains_package "$output" cozystack.kubeovn-webhook
}
@test "suite runtime requirements must resolve in every selector mode" {
for pair in "postgres backupstrategy-controller" "gateway kubeovn-webhook"; do
set -- $pair
suite=$1
requirement=$2
tmp=$(mktemp -d)
cp -r packages/core/platform/sources "$tmp/sources"
mkdir -p "$tmp/suites/$suite"
: > "$tmp/suites/$suite/chainsaw-test.yaml"
hack/select-install.sh --validate "$tmp/sources" "$tmp/suites"
mv "$tmp/sources/$requirement.yaml" "$tmp/$requirement.yaml"
hack/select-install.sh kuberture "$tmp/sources" >/dev/null
for mode in closure disabled; do
assert_output_graph_error "$mode" "$tmp/sources" \
"'cozystack.$requirement' is not a PackageSource" "$suite"
done
if hack/select-install.sh --validate "$tmp/sources" "$tmp/suites" >"$tmp/out" 2>"$tmp/err"; then
echo "expected validation to reject a missing suite runtime requirement" >&2
exit 1
fi
[ ! -s "$tmp/out" ]
grep -Fq "maps to 'cozystack.$requirement', not a PackageSource" "$tmp/err"
rm -rf "$tmp"
done
}
@test "validate passes on the real source graph and suite mapping" {
hack/select-install.sh --validate
}
@test "validate detects a dangling dependency" {
tmp=$(mktemp -d)
mkdir -p "$tmp/sources" "$tmp/suites/foo"
: > "$tmp/suites/foo/chainsaw-test.yaml"
cat > "$tmp/sources/foo-application.yaml" <<'YAML'
apiVersion: cozystack.io/v1alpha1
kind: PackageSource
metadata:
name: cozystack.foo-application
spec:
variants:
- name: default
dependsOn:
- cozystack.does-not-exist
YAML
out="$tmp/out"; err="$tmp/err"
if hack/select-install.sh --validate "$tmp/sources" "$tmp/suites" >"$out" 2>"$err"; then
echo "expected validation to fail on a dangling dependency" >&2
exit 1
fi
[ ! -s "$out" ]
grep -Fq "dangling dependency 'cozystack.does-not-exist'" "$err"
if grep -Fq "found no chainsaw-test.yaml" "$err"; then
echo "positive-control suite discovery failed instead of graph validation" >&2
exit 1
fi
rm -rf "$tmp"
}
@test "validate detects a dependency cycle" {
tmp=$(mktemp -d)
mkdir -p "$tmp/sources" "$tmp/suites/a"
: > "$tmp/suites/a/chainsaw-test.yaml"
cat > "$tmp/sources/a.yaml" <<'YAML'
apiVersion: cozystack.io/v1alpha1
kind: PackageSource
metadata:
name: cozystack.a
spec:
variants:
- name: default
dependsOn:
- cozystack.b
YAML
cat > "$tmp/sources/b.yaml" <<'YAML'
apiVersion: cozystack.io/v1alpha1
kind: PackageSource
metadata:
name: cozystack.b
spec:
variants:
- name: default
dependsOn:
- cozystack.a
YAML
out="$tmp/out"; err="$tmp/err"
if hack/select-install.sh --validate "$tmp/sources" "$tmp/suites" >"$out" 2>"$err"; then
echo "expected validation to fail on a dependency cycle" >&2
exit 1
fi
[ ! -s "$out" ]
grep -Fq "dependency cycle detected involving 'cozystack.a'" "$err"
if grep -Fq "found no chainsaw-test.yaml" "$err"; then
echo "positive-control suite discovery failed instead of graph validation" >&2
exit 1
fi
rm -rf "$tmp"
}
@test "output modes reject a removed runtime source" {
tmp=$(mktemp -d)
cp -r packages/core/platform/sources "$tmp/sources"
mv "$tmp/sources/reloader.yaml" "$tmp/reloader.yaml"
for mode in closure disabled; do
assert_output_graph_error "$mode" "$tmp/sources" \
"dangling dependency 'cozystack.reloader'"
done
yq -i '(.spec.variants[].dependsOn) -= ["cozystack.reloader"]' \
"$tmp/sources/linstor.yaml"
for mode in closure disabled; do
assert_output_graph_error "$mode" "$tmp/sources" \
"required runtime PackageSource(s) not found in $tmp/sources: cozystack.reloader"
done
rm -rf "$tmp"
}
@test "output modes reject a dangling dependency in an unselected package" {
tmp=$(mktemp -d)
cp -r packages/core/platform/sources "$tmp/sources"
yq -i '.spec.variants[0].dependsOn += ["cozystack.does-not-exist"]' \
"$tmp/sources/kafka-application.yaml"
for mode in closure disabled; do
assert_output_graph_error "$mode" "$tmp/sources" \
"dangling dependency 'cozystack.does-not-exist' (referenced by: cozystack.kafka-application)"
done
rm -rf "$tmp"
}
@test "output modes reject selected and unselected dependency cycles" {
tmp=$(mktemp -d)
cp -r packages/core/platform/sources "$tmp/selected"
cp -r packages/core/platform/sources "$tmp/unselected"
yq -i '.spec.variants[0].dependsOn += ["cozystack.postgres-application"]' \
"$tmp/selected/postgres-application.yaml"
yq -i '.spec.variants[0].dependsOn += ["cozystack.kafka-application"]' \
"$tmp/unselected/kafka-application.yaml"
for mode in closure disabled; do
assert_output_graph_error "$mode" "$tmp/selected" \
"dependency cycle detected involving 'cozystack.postgres-application'"
assert_output_graph_error "$mode" "$tmp/unselected" \
"dependency cycle detected involving 'cozystack.kafka-application'"
done
rm -rf "$tmp"
}
@test "validate detects a suite directory with no source mapping" {
tmp=$(mktemp -d)
mkdir -p "$tmp/suites/totally-unmapped-suite"
: > "$tmp/suites/totally-unmapped-suite/chainsaw-test.yaml"
# real graph (so graph checks pass); the only failure is the unmapped suite
if hack/select-install.sh --validate packages/core/platform/sources "$tmp/suites" 2>/dev/null; then
echo "expected validation to fail on an unmapped suite dir" >&2
exit 1
fi
rm -rf "$tmp"
}
@test "validate sees a suite written as chainsaw-test.yml" {
# Chainsaw runs a chainsaw-test.yml suite, so the mapping check has to see
# one too, or an unmapped suite spelled that way passes validation.
tmp=$(mktemp -d)
mkdir -p "$tmp/suites/totally-unmapped-suite"
: > "$tmp/suites/totally-unmapped-suite/chainsaw-test.yml"
err=$(hack/select-install.sh --validate packages/core/platform/sources "$tmp/suites" 2>&1 1>/dev/null) && {
echo "expected validation to fail on an unmapped .yml suite" >&2
exit 1
}
echo "$err" | grep -q "suite 'totally-unmapped-suite' has no PackageSource mapping"
rm -rf "$tmp"
}
@test "validate sees a suite made only of numbered step files" {
# Chainsaw runs a directory of 01-*.yaml step files as a suite when it holds
# no chainsaw-test.*, so the mapping check has to see that layout too. The
# step file is a symlink because Chainsaw counts one.
tmp=$(mktemp -d)
mkdir -p "$tmp/suites/totally-unmapped-suite"
: > "$tmp/install.yaml"
ln -s "$tmp/install.yaml" "$tmp/suites/totally-unmapped-suite/00-install.yaml"
err=$(hack/select-install.sh --validate packages/core/platform/sources "$tmp/suites" 2>&1 1>/dev/null) && {
echo "expected validation to fail on an unmapped step-file suite" >&2
exit 1
}
echo "$err" | grep -q "suite 'totally-unmapped-suite' has no PackageSource mapping"
rm -rf "$tmp"
}
@test "validate ignores what Chainsaw does not load as a step file" {
# Chainsaw skips a step-file name when the entry is a directory, and a name
# with no leading number is not a step file, so neither makes a suite that
# needs a mapping.
tmp=$(mktemp -d)
mkdir -p "$tmp/suites/postgres" "$tmp/suites/totally-unmapped-suite/01-x.yaml"
: > "$tmp/suites/totally-unmapped-suite/-install.yaml"
: > "$tmp/suites/postgres/chainsaw-test.yaml"
hack/select-install.sh --validate packages/core/platform/sources "$tmp/suites"
rm -rf "$tmp"
}
@test "validate sees a directory named chainsaw-test.yaml or .yml" {
# Chainsaw stats chainsaw-test.yaml or .yml without checking its type, so a
# directory of that name fails discovery loudly. Listing its suite keeps
# that failure where a run reaches it instead of leaving the suite out.
for name in chainsaw-test.yaml chainsaw-test.yml; do
tmp=$(mktemp -d)
mkdir -p "$tmp/suites/totally-unmapped-suite/$name"
: > "$tmp/suites/totally-unmapped-suite/$name/x"
err=$(hack/select-install.sh --validate packages/core/platform/sources "$tmp/suites" 2>&1 1>/dev/null) && {
echo "expected validation to fail on an unmapped suite with a $name directory" >&2
exit 1
}
echo "$err" | grep -q "suite 'totally-unmapped-suite' has no PackageSource mapping"
rm -rf "$tmp"
done
}
@test "validate sees a suite nested below a top-level directory" {
# Chainsaw walks every directory under the one it is handed, so a test in a
# subdirectory runs as part of its top-level directory, which then needs a
# mapping like any other suite.
tmp=$(mktemp -d)
mkdir -p "$tmp/suites/totally-unmapped-suite/cases"
: > "$tmp/suites/totally-unmapped-suite/cases/chainsaw-test.yaml"
err=$(hack/select-install.sh --validate packages/core/platform/sources "$tmp/suites" 2>&1 1>/dev/null) && {
echo "expected validation to fail on an unmapped nested suite" >&2
exit 1
}
echo "$err" | grep -q "suite 'totally-unmapped-suite' has no PackageSource mapping"
rm -rf "$tmp"
}
@test "validate fails when the suite listing itself fails" {
# A failed find and an empty suites dir used to read the same: the error
# went to /dev/null and the pipeline reported sort's status, so the mapping
# check validated nothing and passed.
tmp=$(mktemp -d)
mkdir "$tmp/bin"
printf '#!/bin/sh\necho "find: broken fixture" >&2\nexit 1\n' > "$tmp/bin/find"
chmod +x "$tmp/bin/find"
err=$(PATH="$tmp/bin:$PATH" hack/select-install.sh --validate 2>&1 1>/dev/null) && {
echo "expected validate to fail when find fails" >&2
exit 1
}
echo "$err" | grep -q "select-install: find failed listing the suites"
rm -rf "$tmp"
}
@test "validate detects a suite mapped to a source absent from the graph" {
# suite_to_source() hardcodes securitygroup -> cozystack.securitygroup-controller;
# a sources dir without that PackageSource must fail validation (fail closed),
# pinning that the elif validates every mapping, including hardcoded ones,
# against the graph.
tmp=$(mktemp -d)
mkdir -p "$tmp/sources" "$tmp/suites/securitygroup"
: > "$tmp/suites/securitygroup/chainsaw-test.yaml"
cat > "$tmp/sources/standalone.yaml" <<'YAML'
apiVersion: cozystack.io/v1alpha1
kind: PackageSource
metadata:
name: cozystack.standalone
spec:
variants:
- name: default
YAML
err=$(hack/select-install.sh --validate "$tmp/sources" "$tmp/suites" 2>&1 1>/dev/null) && {
echo "expected validation to fail on a mapping to a missing source" >&2
exit 1
}
echo "$err" | grep -q "maps to 'cozystack.securitygroup-controller', not a PackageSource"
rm -rf "$tmp"
}
@test "validate fails when the suites dir does not exist" {
tmp=$(mktemp -d)
# real graph passes the graph checks; a missing suites dir must still FAIL
# (find would yield nothing and silently pass the mapping check otherwise).
if hack/select-install.sh --validate packages/core/platform/sources "$tmp/does-not-exist" 2>/dev/null; then
echo "expected validate to fail on a missing suites dir" >&2
exit 1
fi
rm -rf "$tmp"
}
@test "validate fails when suite discovery fails" {
tmp=$(mktemp -d)
mkdir -p "$tmp/bin" "$tmp/suites/one"
: > "$tmp/suites/one/chainsaw-test.yaml"
cat > "$tmp/bin/find" <<'SH'
#!/bin/sh
exit 42
SH
chmod +x "$tmp/bin/find"
out=$(mktemp); err=$(mktemp)
if PATH="$tmp/bin:$PATH" hack/select-install.sh --validate \
packages/core/platform/sources "$tmp/suites" >"$out" 2>"$err"; then
echo "expected validate to fail when find fails" >&2
exit 1
fi
[ ! -s "$out" ]
grep -Fq "find failed listing the suites" "$err"
rm -rf "$tmp"; rm -f "$out" "$err"
}
@test "yq producer failures cannot become a successful partial graph" {
tmp=$(mktemp -d)
mkdir -p "$tmp/bin"
real_yq=$(command -v yq)
cat > "$tmp/bin/yq" <<'SH'
#!/bin/sh
case "${FAIL_YQ:-}" in
names)
case "$*" in
*metadata.name*) echo cozystack.partial; exit 42 ;;
esac
;;
dependencies)
case "$*" in
*spec.variants*) printf 'cozystack.partial\tcozystack.missing\n'; exit 42 ;;
esac
;;
esac
exec "$REAL_YQ" "$@"
SH
chmod +x "$tmp/bin/yq"
for stage in names dependencies; do
out="$tmp/$stage.out"; err="$tmp/$stage.err"
if REAL_YQ="$real_yq" FAIL_YQ="$stage" PATH="$tmp/bin:$PATH" \
hack/select-install.sh postgres >"$out" 2>"$err"; then
echo "expected the $stage yq failure to propagate" >&2
exit 1
fi
[ ! -s "$out" ]
grep -Fq "yq failed while reading PackageSource" "$err"
done
rm -rf "$tmp"
}
@test "closure fails closed when the engine PackageSource is absent" {
tmp=$(mktemp -d)
mkdir -p "$tmp/sources"
# a lone app source, with no cozystack.cozystack-engine in the graph
cat > "$tmp/sources/foo-application.yaml" <<'YAML'
apiVersion: cozystack.io/v1alpha1
kind: PackageSource
metadata:
name: cozystack.foo-application
spec:
variants:
- name: default
YAML
if hack/select-install.sh "foo" "$tmp/sources" 2>/dev/null; then
echo "expected a non-zero exit when the engine source is missing" >&2
exit 1
fi
rm -rf "$tmp"
}
# --disabled emits the complement of the closure: what bundles.disabledPackages
# must hold so an ordinary platform variant installs only what the suites need.
@test "disabled mode: closure and complement partition the source list" {
# Count names only after yq itself has succeeded; dash has no pipefail.
names=$(yq -rN '.metadata.name | select(. != null and . != "")' \
packages/core/platform/sources/*.yaml)
total=$(printf '%s\n' "$names" | sort -u | wc -l | tr -d ' ')
keep_output=$(hack/select-install.sh "postgres")
drop_output=$(hack/select-install.sh --disabled "postgres")
keep=$(printf '%s\n' "$keep_output" | wc -w | tr -d ' ')
drop=$(printf '%s\n' "$drop_output" | wc -w | tr -d ' ')
sum=$((keep + drop))
if [ "$sum" -ne "$total" ]; then
echo "closure ($keep) + complement ($drop) = $sum, but there are $total PackageSources" >&2
exit 1
fi
inventory=$(printf '%s\n' "$names" | sort -u)
partition=$(printf '%s\n%s\n' "$keep_output" "$drop_output" | tr ' ' '\n' | sort -u)
[ "$partition" = "$inventory" ]
}
@test "disabled mode accepts a valid inventory with an empty complement" {
tmp=$(mktemp -d)
mkdir -p "$tmp/sources"
keep=$(hack/select-install.sh postgres)
for source in $keep; do
name=${source#cozystack.}
cp "packages/core/platform/sources/$name.yaml" "$tmp/sources/"
done
output=$(hack/select-install.sh --disabled postgres "$tmp/sources")
[ -z "$output" ]
rm -rf "$tmp"
}
@test "disabled mode: never lists a package the closure keeps" {
keep=$(hack/select-install.sh "postgres kafka")
drop=$(hack/select-install.sh --disabled "postgres kafka")
for k in $keep; do
case " $drop " in
*" $k "*) echo "package '$k' is both kept and disabled" >&2; exit 1 ;;
esac
done
}
# A declared dependency of something kept can never land in the complement.
# Assert the property over every suite instead of citing an unreachable pair.
@test "disabled mode: no kept package has a dependency in the complement" {
deps=$(mktemp)
yq -rN '.metadata.name as $n | .spec.variants[]?.dependsOn[]? | select(. != null and . != "") | $n + " " + .' \
packages/core/platform/sources/*.yaml > "$deps"
for suite in $(find hack/e2e-chainsaw -mindepth 2 -maxdepth 2 -name chainsaw-test.yaml \
| sed -e 's,^hack/e2e-chainsaw/,,' -e 's,/chainsaw-test\.yaml$,,'); do
keep=$(hack/select-install.sh "$suite")
drop=$(hack/select-install.sh --disabled "$suite")
for k in $keep; do
for d in $(awk -v owner="$k" '$1 == owner {print $2}' "$deps"); do
case " $drop " in
*" $d "*)
echo "suite '$suite': kept '$k' depends on '$d', which is in the disable list" >&2
rm -f "$deps"; exit 1 ;;
esac
done
done
done
rm -f "$deps"
}
# Direct seeds correspond to unconditional install steps, even when another
# baseline source also reaches them through dependsOn.
@test "disabled mode: never disables the direct runtime baseline" {
drop=$(hack/select-install.sh --disabled "postgres")
for b in cozystack.cozystack-engine cozystack.flux-shard-operator cozystack.cozystack-basics \
cozystack.tenant-application cozystack.etcd-application \
cozystack.ingress-application cozystack.monitoring-application \
cozystack.seaweedfs-application cozystack.linstor \
cozystack.kubevirt-cdi cozystack.metallb cozystack.capi-operator \
cozystack.capi-provider-bootstrap-kubeadm \
cozystack.capi-provider-core cozystack.capi-provider-cp-kamaji \
cozystack.capi-provider-infra-kubevirt cozystack.keycloak \
cozystack.keycloak-operator cozystack.multus \
cozystack.cozystack-scheduler cozystack.linstor-scheduler \
cozystack.flux-plunger cozystack.cozy-proxy cozystack.ouroboros; do
case " $drop " in
*" $b "*) echo "baseline package '$b' is in the disable list" >&2; exit 1 ;;
esac
done
}
@test "runtime baseline reaches every derived controller and CRD prerequisite" {
keep=$(hack/select-install.sh "postgres")
for required in cozystack.cert-manager cozystack.networking \
cozystack.gateway-api-crds cozystack.etcd-operator \
cozystack.grafana-operator cozystack.postgres-operator \
cozystack.victoria-metrics-operator cozystack.prometheus-operator-crds \
cozystack.vertical-pod-autoscaler cozystack.reloader \
cozystack.snapshot-controller cozystack.gateway-application \
cozystack.info-application cozystack.kamaji cozystack.kubevirt; do
if ! printf '%s\n' "$keep" | tr ' ' '\n' | grep -Fxq "$required"; then
echo "runtime baseline is missing derived prerequisite '$required'" >&2
exit 1
fi
done
}
@test "runtime baseline fails closed when a provider prerequisite edge is lost" {
tmp=$(mktemp -d)
cp -r packages/core/platform/sources "$tmp/sources"
yq -i '(.spec.variants[].dependsOn) -= ["cozystack.kamaji"]' \
"$tmp/sources/capi-provider-cp-kamaji.yaml"
out="$tmp/out"; err="$tmp/err"
if hack/select-install.sh postgres "$tmp/sources" >"$out" 2>"$err"; then
echo "expected a missing Kamaji prerequisite to fail closed" >&2
exit 1
fi
[ ! -s "$out" ]
grep -Fq "runtime baseline dependency closure is missing: cozystack.kamaji" "$err"
cp packages/core/platform/sources/capi-provider-cp-kamaji.yaml \
"$tmp/sources/capi-provider-cp-kamaji.yaml"
yq -i '(.spec.variants[].dependsOn) -= ["cozystack.kubevirt"]' \
"$tmp/sources/capi-provider-infra-kubevirt.yaml"
if hack/select-install.sh postgres "$tmp/sources" >"$out" 2>"$err"; then
echo "expected a missing KubeVirt prerequisite to fail closed" >&2
exit 1
fi
[ ! -s "$out" ]
grep -Fq "runtime baseline dependency closure is missing: cozystack.kubevirt" "$err"
rm -rf "$tmp"
}
@test "runtime baseline fails closed when storage prerequisite edges are lost" {
tmp=$(mktemp -d)
cp -r packages/core/platform/sources "$tmp/sources"
yq -i '(.spec.variants[].dependsOn) -= ["cozystack.reloader", "cozystack.snapshot-controller"]' \
"$tmp/sources/linstor.yaml"
out="$tmp/out"; err="$tmp/err"
if hack/select-install.sh postgres "$tmp/sources" >"$out" 2>"$err"; then
echo "expected missing LINSTOR prerequisites to fail closed" >&2
exit 1
fi
[ ! -s "$out" ]
grep -Fq "cozystack.reloader" "$err"
grep -Fq "cozystack.snapshot-controller" "$err"
rm -rf "$tmp"
}
@test "generated Helm values fixture matches the current disable list" {
generated_raw=$(hack/select-install.sh --disabled postgres)
generated=$(printf '%s\n' "$generated_raw" | tr ' ' '\n' | sort)
fixture_raw=$(yq -r '.bundles.disabledPackages[]' \
packages/core/platform/tests/fixtures/selective-install-postgres-values.yaml)
fixture=$(printf '%s\n' "$fixture_raw" | sort)
if [ "$generated" != "$fixture" ]; then
echo "selective-install-postgres-values.yaml is stale; regenerate it from select-install.sh" >&2
exit 1
fi
tmp=$(mktemp -d)
cp -r packages/core/platform/sources "$tmp/sources"
cat > "$tmp/sources/fixture-drift.yaml" <<'YAML'
apiVersion: cozystack.io/v1alpha1
kind: PackageSource
metadata:
name: cozystack.fixture-drift
spec:
variants:
- name: default
YAML
drifted_raw=$(hack/select-install.sh --disabled postgres "$tmp/sources")
drifted=$(printf '%s\n' "$drifted_raw" | tr ' ' '\n' | sort)
if [ "$drifted" = "$fixture" ]; then
echo "a new PackageSource did not invalidate the generated fixture" >&2
exit 1
fi
printf '%s\n' "$drifted" | grep -Fxq cozystack.fixture-drift
rm -rf "$tmp"
}
# An empty closure would make the complement the entire platform. As an install
# instruction that is a platform with no packages, so it must refuse rather than
# emit it.
@test "disabled mode: refuses an empty selection" {
out=$(mktemp); err=$(mktemp)
if hack/select-install.sh --disabled "" >"$out" 2>"$err"; then
echo "expected a non-zero exit for an empty selection, got: $(cat "$out")" >&2
rm -f "$out" "$err"; exit 1
fi
grep -q "refusing to emit a disable list" "$err" \
|| { echo "expected the refusal message, got: $(cat "$err")" >&2; rm -f "$out" "$err"; exit 1; }
rm -f "$out" "$err"
}