mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 19:01:54 +00:00
Replace the previous OR-based assertion in scripts/tests/environment (which trivially passed because the Nexus log always grows from health probes) with a richer set of helpers split across two files under utils/ for SRP/SPOT. scripts/tests/environment/utils/cache.sh (new): * expected_system_proxy_for_runtime maps DEV_RUNTIME_IMAGE to the Nexus proxy that handles its system package manager (apt-debian, apt-ubuntu, yum-fedora, yum-rocky; empty for Manjaro/Arch where pacman is not cached). * nexus_repo_requests counts only /repository/<name>/... entries in the Nexus request log, optionally scoped to one proxy. Filters out health probes, admin REST calls, and Web-UI traffic. * registry_cache_flows counts every cache-flow status (HIT, MISS, REVALIDATED, UPDATING, STALE) so cold deploys also count. * assert_caches_used fails with a per-cache breakdown when any of three independent conditions is violated: total Nexus repo traffic = 0, the runtime's expected system proxy has 0 cached requests, or the deploy itself produced 0 registry-cache flows. * probe_caches actively fetches a known upstream resource through each cache (apt-debian/dists/bookworm/Release for the package cache, /ca.crt for the registry cache). * probe_did_inner_build drives a throwaway docker build whose apt-get update runs against deb.debian.org. The build container is placed on the compose network and pinned via --add-host to the frontend IP, exactly like the runtime override that roles/sys-svc-compose/files/compose.py injects for app builds. After the build the test snapshots ``docker logs infinito-package-cache-frontend`` and asserts the frontend recorded new requests, proving DiD apt traffic flows through nginx -> Nexus apt-debian and not directly to upstream. DOCKER_BUILDKIT=0 because BuildKit refuses --network=<compose-net>. scripts/tests/environment/utils/common.sh (renamed from lib.sh): keeps the bootstrap, constants, inventory inspect, and assert_http_status; cache helpers moved out so the file is single-concern. scripts/tests/environment/07_deploy_minimal.sh sources both files; order is snapshot, deploy, probe_caches, probe_did_inner_build, snapshot, assert_caches_used. Active probes contribute traffic that the AND assertion verifies, so the assertion is robust on deploys that do not naturally trigger inner-build apt traffic. .github/workflows/test-environment.yml: propagate DEV_RUNTIME_IMAGE into the inner ``docker run`` so per-distro probe target and proxy expectation resolve correctly inside the orchestrator. .gitignore + .dockerignore: also ignore .claude/settings.local.json, which compose mounts as /dev/null and which the classic docker builder would otherwise try (and fail) to read into the build context. Verified end-to-end with five iterations of ``make act-workflow ACT_WORKFLOW=.github/workflows/test-environment.yml ACT_JOB=test-environment ACT_MATRIX='dev_runtime_image:debian:bookworm'``. The final iteration reports +81 apt-debian, +235 registry-cache flows, +13 frontend log lines from the DiD probe, and "[OK] both cache stacks observed activity" plus "[OK] DiD inner-build flowed through cache frontend".
127 lines
4.1 KiB
Bash
Executable File
127 lines
4.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Validate pre-commit hook enforcement and --no-verify bypass.
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# shellcheck source=scripts/tests/environment/utils/common.sh
|
|
source "${SCRIPT_DIR}/utils/common.sh"
|
|
|
|
cd "${REPO_ROOT}"
|
|
|
|
ORIGINAL_GIT_USER_NAME="$(git config --local --get user.name || true)"
|
|
ORIGINAL_GIT_USER_EMAIL="$(git config --local --get user.email || true)"
|
|
|
|
if [[ -z "${ORIGINAL_GIT_USER_NAME}" ]]; then
|
|
git config --local user.name "Environment Test"
|
|
fi
|
|
|
|
if [[ -z "${ORIGINAL_GIT_USER_EMAIL}" ]]; then
|
|
git config --local user.email "environment-test@example.invalid"
|
|
fi
|
|
|
|
if ORIGINAL_REF="$(git symbolic-ref --quiet --short HEAD)"; then
|
|
:
|
|
else
|
|
ORIGINAL_REF="$(git rev-parse --verify HEAD)"
|
|
fi
|
|
|
|
TEST_BRANCH="test/pre-commit-hook-$(date +%s)"
|
|
WORKTREE_STASH=""
|
|
|
|
if [[ -n "$(git status --short)" ]]; then
|
|
WORKTREE_STASH="environment-pre-commit-hook-$(date +%s)"
|
|
echo "Stashing existing repository changes before commit hook validation."
|
|
git stash push --include-untracked --message "${WORKTREE_STASH}" >/dev/null
|
|
fi
|
|
|
|
cleanup() {
|
|
local exit_code="$1"
|
|
set +e
|
|
|
|
if git rev-parse --verify "${TEST_BRANCH}" >/dev/null 2>&1; then
|
|
local current_ref
|
|
current_ref="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
if [[ "${current_ref}" == "${TEST_BRANCH}" ]]; then
|
|
git reset --hard HEAD >/dev/null 2>&1 || true
|
|
git clean -fd >/dev/null 2>&1 || true
|
|
git checkout "${ORIGINAL_REF}" >/dev/null 2>&1 || true
|
|
fi
|
|
git branch -D "${TEST_BRANCH}" >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
if [[ -n "${ORIGINAL_GIT_USER_NAME}" ]]; then
|
|
git config --local user.name "${ORIGINAL_GIT_USER_NAME}" >/dev/null 2>&1 || true
|
|
else
|
|
git config --local --unset user.name >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
if [[ -n "${ORIGINAL_GIT_USER_EMAIL}" ]]; then
|
|
git config --local user.email "${ORIGINAL_GIT_USER_EMAIL}" >/dev/null 2>&1 || true
|
|
else
|
|
git config --local --unset user.email >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
if [[ -n "${WORKTREE_STASH}" ]] && git stash list | grep -F "${WORKTREE_STASH}" >/dev/null 2>&1; then
|
|
if ! git stash pop --index >/dev/null 2>&1; then
|
|
echo "[FAIL] Failed to restore the stashed repository changes after commit validation." >&2
|
|
exit_code=1
|
|
fi
|
|
fi
|
|
|
|
exit "${exit_code}"
|
|
}
|
|
trap 'cleanup "$?"' EXIT
|
|
|
|
git checkout -b "${TEST_BRANCH}"
|
|
|
|
# --- Step 1: Create a failing unit test and verify the hook blocks the commit.
|
|
echo "Creating a unit test that fails intentionally."
|
|
cat >tests/unit/test_pre_commit_hook_failure.py <<'EOF'
|
|
import unittest
|
|
|
|
|
|
class TestPreCommitHookFailure(unittest.TestCase):
|
|
def test_intentional_failure(self) -> None:
|
|
self.fail("intentional failure to verify pre-commit hook enforcement")
|
|
EOF
|
|
git add tests/unit/test_pre_commit_hook_failure.py
|
|
|
|
echo "Attempting to commit the failing unit test — pre-commit hook must block it."
|
|
if git commit -m "test: failing unit test (should be blocked)"; then
|
|
echo "[FAIL] Commit should have been blocked by pre-commit hook." >&2
|
|
exit 1
|
|
fi
|
|
echo "[OK] Commit correctly blocked by pre-commit hook."
|
|
|
|
# --- Step 2: Commit with --no-verify to bypass the hook.
|
|
echo "Committing the failing unit test with --no-verify to bypass the hook."
|
|
git commit --no-verify -m "test: failing unit test committed with --no-verify"
|
|
echo "[OK] Commit with --no-verify succeeded."
|
|
|
|
# --- Step 3: Revert the bypass commit.
|
|
echo "Reverting the --no-verify commit."
|
|
git revert --no-edit HEAD
|
|
echo "[OK] Revert succeeded."
|
|
|
|
# --- Step 4: Create a passing unit test and commit without --no-verify.
|
|
echo "Creating a clean unit test that passes."
|
|
cat >tests/unit/test_pre_commit_hook_success.py <<'EOF'
|
|
import unittest
|
|
|
|
|
|
class TestPreCommitHookSuccess(unittest.TestCase):
|
|
def test_success(self) -> None:
|
|
self.assertTrue(True)
|
|
EOF
|
|
git add tests/unit/test_pre_commit_hook_success.py
|
|
|
|
echo "Committing the passing unit test without --no-verify — pre-commit hook must pass."
|
|
git commit -m "test: passing unit test committed via pre-commit hook"
|
|
echo "[OK] Commit without --no-verify succeeded."
|
|
|
|
echo "Returning to the original ref and deleting the temporary test branch."
|
|
git checkout "${ORIGINAL_REF}"
|
|
git branch -D "${TEST_BRANCH}"
|
|
trap - EXIT
|
|
cleanup 0
|