2fe634141f
gates / gates (push) Successful in 45s
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.
168 lines
7.7 KiB
Bash
168 lines
7.7 KiB
Bash
#!/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
|