Files
felhom-controller/controller/scripts/test_build_push_once.sh
T
admin 2fe634141f
gates / gates (push) Successful in 45s
v0.329.0: each app reaches only its own appdata folder; empty allow-list; drive retirement asks the agent first; plain no-durable-id refusal; :latest only for a release
R-1142: the HDD_PATH root is appdata/<own folder>, with a compiled-in folder map (paperless-ngx ->
paperless). R-981: the allow-list is empty (homepage's socket served no Felhom feature). R-1063:
finalizeDecommissionWith asks the agent before it marks the drive or moves the default; the 403/409
come back typed and reach the household as a plain 409. R-1069: the agent's no-durable-id format
refusal is ErrNoDurableID and a plain sentence. R-1143: build.sh carries :latest only for x.y.z.
Each pinned by a red-proofed test. MinAgent 0.158.1.
2026-10-17 23:11:56 +02:00

168 lines
7.7 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# A VERSION NUMBER IS PUSHED ONCE — the test for build.sh's registry check (v0.316.0).
#
# WHY IT EXISTS: 0.315.0 was built and pushed three times from three different commits. A box that
# pulled between the first and the last runs different code under the same number and will never
# fetch the fix, because to the box that number is already current. The check in build.sh is the
# mechanism; this is the test that it convicts.
#
# HOW IT TESTS WITHOUT A REGISTRY: a fake `docker` is put first on PATH. Every case drives the REAL
# build.sh, so the test cannot pass while the check has been deleted — which is the red-proof.
# Nothing is ever pushed: the fake refuses `build` and `push` loudly, so a build that got past the
# check would fail the test rather than reach the network.
#
# Run from controller/: bash scripts/test_build_push_once.sh
set -u
HERE="$(cd "$(dirname "$0")" && pwd)"
BUILD_SH="$(dirname "$HERE")/build.sh"
fails=0; ran=0
[[ -f "$BUILD_SH" ]] || { echo "FAIL: build.sh not found at $BUILD_SH"; exit 1; }
# A fake `docker` whose answer to `manifest inspect` is set by FAKE_MODE.
make_fake_docker() {
local dir="$1"
mkdir -p "$dir"
cat > "$dir/docker" <<'FAKE'
#!/bin/bash
case "$1 $2" in
"manifest inspect")
case "${FAKE_MODE}" in
present) echo '{"schemaVersion":2}'; exit 0 ;;
absent) echo "manifest unknown: manifest unknown" >&2; exit 1 ;;
down) echo "Get \"https://gitea.dooplex.hu/v2/\": dial tcp: connection refused" >&2; exit 1 ;;
denied) echo "unauthorized: authentication required" >&2; exit 1 ;;
esac ;;
"build "*|"buildx "*|"push "*)
echo "FAKE-DOCKER-REACHED: $*" ; exit 0 ;;
esac
# anything else (version checks etc.) succeeds quietly
exit 0
FAKE
chmod +x "$dir/docker"
}
# Run build.sh with the fake docker, from a throwaway cwd, and capture everything.
run_build() {
local mode="$1"; shift
local tmp; tmp="$(mktemp -d)"
make_fake_docker "$tmp/bin"
# build.sh is run from a COPY in the throwaway dir, as the build dir runs it (through its symlink): its workspace
# is <its own dir>/workspace, and run in place that is inside the source it copies (cp: into itself, 2026-10-18).
cp "$BUILD_SH" "$tmp/build.sh"
FAKE_MODE="$mode" PATH="$tmp/bin:$PATH" bash "$tmp/build.sh" "$@" >"$tmp/out" 2>&1
rc=$?
out="$(cat "$tmp/out")"
rm -rf "$tmp"
return $rc
}
check() {
local name="$1" want_rc="$2" want_text="$3" forbid_text="${4:-}"
ran=$((ran+1))
if [[ "$rc" != "$want_rc" ]]; then
fails=$((fails+1)); echo " FAIL $name: exit $rc, want $want_rc"
echo "$out" | tail -6 | sed 's/^/ /'; return
fi
if [[ -n "$want_text" ]] && ! grep -qi -- "$want_text" <<<"$out"; then
fails=$((fails+1)); echo " FAIL $name: output does not contain '$want_text'"
echo "$out" | tail -6 | sed 's/^/ /'; return
fi
if [[ -n "$forbid_text" ]] && grep -qi -- "$forbid_text" <<<"$out"; then
fails=$((fails+1)); echo " FAIL $name: output contains '$forbid_text', which must not happen"
echo "$out" | tail -6 | sed 's/^/ /'; return
fi
echo " ok $name"
}
echo "build.sh — a version number is pushed once, from a clean tree"
# 1. The tag is already in the registry -> refuse, before building, and push nothing.
run_build present 0.999.0 --push
check "--push, tag present -> refuses" 1 "already exists in the registry" "FAKE-DOCKER-REACHED"
# 2. The tag is new -> say so and carry on INTO the build.
# The exit code is deliberately not asserted: past the check, build.sh runs the real build steps,
# which cannot complete in a test sandbox. What matters is that the check let it through, so the
# assertion is its verdict line plus the ABSENCE of a refusal.
run_build absent 0.999.0 --push
check "--push, tag absent -> proceeds" "$rc" "is not present in the registry" "already exists in the registry"
# 3. --multiarch gets the same refusal (the task's edge case).
run_build present 0.999.0 --multiarch
check "--multiarch, tag present -> refuses" 1 "already exists in the registry" "FAKE-DOCKER-REACHED"
# 4. The registry cannot be asked -> refuse. "I could not ask" is not proof the tag is new.
run_build down 0.999.0 --push
check "--push, registry unreachable -> refuses" 1 "Could not establish whether" "FAKE-DOCKER-REACHED"
# 5. An auth failure is just as unanswered as a network failure.
run_build denied 0.999.0 --push
check "--push, auth failure -> refuses" 1 "Could not establish whether" "FAKE-DOCKER-REACHED"
# 6. A LOCAL build is untouched — reusing a version while iterating is the normal case, and the
# check must not even run (no registry question is asked, so `present` must not refuse).
run_build present 0.999.0
check "local build, tag present -> not refused" 0 "" "already exists in the registry"
# ── THE SOURCE TREE MUST BE CLEAN AND PUSHED (v0.316.4) ─────────────────────────────────────────
# Earned the same evening: a build from the shared clone, which held 31 files of another session's
# work in progress, published their unreleased code under this repo's version number.
scratch_repo() {
local d; d="$(mktemp -d)"
git -C "$d" init -q 2>/dev/null
git -C "$d" config user.email t@t; git -C "$d" config user.name t
mkdir -p "$d/controller"; echo x > "$d/controller/f"
git -C "$d" add -A >/dev/null; git -C "$d" commit -qm one >/dev/null
git -C "$d" update-ref refs/remotes/origin/main HEAD
echo "$d"
}
run_build_in() { # $1 = mode, $2 = repo dir, rest = args
local mode="$1" repo="$2"; shift 2
local tmp; tmp="$(mktemp -d)"
make_fake_docker "$tmp/bin"
cp "$BUILD_SH" "$tmp/build.sh"
FAKE_MODE="$mode" FELHOM_REPO_DIR="$repo" PATH="$tmp/bin:$PATH" bash "$tmp/build.sh" "$@" >"$tmp/out" 2>&1
rc=$?; out="$(cat "$tmp/out")"; rm -rf "$tmp"; return $rc
}
CLEAN_REPO="$(scratch_repo)"
run_build_in absent "$CLEAN_REPO" 0.999.0 --push
check "clean + at origin/main -> proceeds" "$rc" "Source tree is clean and at origin/main" "refusing to push"
echo "dirt" > "$CLEAN_REPO/controller/uncommitted"
run_build_in absent "$CLEAN_REPO" 0.999.0 --push
check "dirty tree -> refuses" 1 "uncommitted change" "FAKE-DOCKER-REACHED"
rm -f "$CLEAN_REPO/controller/uncommitted"
echo y > "$CLEAN_REPO/controller/f2"
git -C "$CLEAN_REPO" add -A >/dev/null; git -C "$CLEAN_REPO" commit -qm two >/dev/null
run_build_in absent "$CLEAN_REPO" 0.999.0 --push
check "ahead of origin/main -> refuses" 1 "not at origin/main" "FAKE-DOCKER-REACHED"
rm -rf "$CLEAN_REPO"
NOT_A_REPO="$(mktemp -d)"; mkdir -p "$NOT_A_REPO/controller"
run_build_in absent "$NOT_A_REPO" 0.999.0 --push
check "not a git checkout -> refuses" 1 "not a git checkout" "FAKE-DOCKER-REACHED"
rm -rf "$NOT_A_REPO"
# ── :latest MOVES WITH A RELEASE ONLY (R-1143, 2026-10-18) ──────────────────────────────────────────
# rc.1–rc.5 of 0.328.0 each moved :latest. --print-tags prints the very TAGS array the push and multiarch legs use.
# RED-PROOF: with the release-tag condition removed (every build adds :latest), the two rc/dev cases fail.
for v in 0.999.0-rc.1 0.999.0-rc.12 dev 0.999; do
run_build absent "$v" --print-tags
check "--print-tags $v -> no :latest" 0 "felhom-controller:$v" "felhom-controller:latest"
done
run_build absent 0.999.0 --print-tags
check "--print-tags 0.999.0 (a release) -> :latest too" 0 "felhom-controller:latest" ""
grep -q 'docker push "${IMAGE}:latest"' "$BUILD_SH" && { fails=$((fails+1)); echo " FAIL a push of :latest outside the TAGS array"; }
ran=$((ran+1))
echo
if [[ $fails -eq 0 ]]; then
echo "all $ran cases behaved — a pushed version cannot be overwritten"
exit 0
fi
echo "$fails of $ran cases FAILED"
exit 1