#!/usr/bin/env bash # # scripts/setup.sh: argument parsing, --headless wiring, detection # ladder, and prompt_choice regression. Runs hermetically against a # sandboxed $HOME with stubbed scripts; no network, no real Gitea. set -euo pipefail here="$(cd "$(dirname "$0")" && pwd -P)" root="$here/.." pass=0 fail=0 assert() { local msg="$1"; shift if "$@"; then printf '[ok] %s\n' "$msg" pass=$((pass + 1)) else printf '[FAIL] %s\n' "$msg" fail=$((fail + 1)) fi } assert 'setup.sh parses as valid bash' bash -n "$root/scripts/setup.sh" help_out="$(bash "$root/scripts/setup.sh" --help 2>&1)" assert '--help prints the Usage header' \ bash -c "printf '%s' \"$help_out\" | grep -q '^Usage: just setup'" assert '--help documents --headless' \ bash -c "printf '%s' \"$help_out\" | grep -q -- '--headless'" assert '--help documents FORGE_SETUP_YES' \ bash -c "printf '%s' \"$help_out\" | grep -q 'FORGE_SETUP_YES'" set +e bash "$root/scripts/setup.sh" --not-a-flag >/dev/null 2>"$here/.bad.err" rc=$? set -e assert 'unknown option exits non-zero' bash -c "[ $rc -ne 0 ]" assert 'unknown option prints a clear error' \ grep -q 'unknown option' "$here/.bad.err" rm -f "$here/.bad.err" # --- Sandbox with stubbed dependencies ------------------------------ tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT mkdir -p "$tmp/scripts" "$tmp/home" "$tmp/tokens" touch "$tmp/Justfile" cp "$root/scripts/setup.sh" "$tmp/scripts/setup.sh" cp "$root/scripts/common.sh" "$tmp/scripts/common.sh" cat >"$tmp/.env.example" <<'EOF' FORGE_GITEA_URL="http://127.0.0.1:1" FORGE_GITEA_ORG="x" FORGE_GITEA_USERNAME="sandbox-user" FORGE_ORCHESTRATOR_REPO_URL="http://127.0.0.1:1/x/y.git" FORGE_WORKSPACE_ROOT="." FSDGG_CLI_CLIENT_ID="sandbox-client" FSDGG_CLI_REDIRECT_URI="http://127.0.0.1:38111/callback" EOF cp "$tmp/.env.example" "$tmp/.env" cat >"$tmp/scripts/doctor.sh" <<'EOF' #!/usr/bin/env bash echo "[doctor] stubbed" EOF chmod +x "$tmp/scripts/doctor.sh" cat >"$tmp/scripts/forge_login.sh" <"$tmp/forge_login.args" mkdir -p "$tmp/tokens" cat >"$tmp/tokens/client-auth.json" <"$tmp/scripts/install-git-credential-helper.sh" <<'EOF' #!/usr/bin/env bash echo "[install-helper stub] ok" EOF chmod +x "$tmp/scripts/install-git-credential-helper.sh" cat >"$tmp/scripts/forge_auth.py" <<'EOF' #!/usr/bin/env python3 import sys if len(sys.argv) >= 2 and sys.argv[1] == "status": sys.exit(1) sys.exit(0) EOF chmod +x "$tmp/scripts/forge_auth.py" mkdir -p "$tmp/fakebin" cat >"$tmp/fakebin/curl" <<'EOF' #!/usr/bin/env bash for arg in "$@"; do case "$arg" in *"/api/v1/version") echo '{"version":"0.0.0-stub"}' exit 0;; esac done exec /usr/bin/curl "$@" EOF chmod +x "$tmp/fakebin/curl" cat >"$tmp/fakebin/git" <"$tmp/setup_guard.out" 2>"$tmp/setup_guard.err" rc=$? set -e assert 'headless + FORGE_SETUP_YES + no session -> exits non-zero (no hang)' \ bash -c "[ $rc -ne 0 ]" assert 'headless + FORGE_SETUP_YES guard message is actionable' \ grep -q 'cannot complete a fresh login under --headless + FORGE_SETUP_YES=1' \ "$tmp/setup_guard.err" assert 'headless + FORGE_SETUP_YES guard does NOT invoke forge_login.sh' \ bash -c "[ ! -f \"$tmp/forge_login.args\" ]" # --- --headless (interactive) + no stored session: forge_login.sh --no-browser rm -f "$tmp/forge_login.args" set +e env -u FORGE_SETUP_YES bash "$tmp/scripts/setup.sh" --headless \ >"$tmp/setup_headless.out" 2>"$tmp/setup_headless.err" rc=$? set -e assert 'headless (interactive) exits 0 in sandbox' bash -c "[ $rc -eq 0 ]" assert 'headless (interactive) invokes forge_login.sh' \ test -f "$tmp/forge_login.args" assert 'headless (interactive) forwards --no-browser' \ grep -qxF -- '--no-browser' "$tmp/forge_login.args" # --- Default (browser) must NOT forward --no-browser rm -f "$tmp/forge_login.args" "$tmp/tokens/client-auth.json" set +e env -u FORGE_SETUP_YES bash "$tmp/scripts/setup.sh" \ >"$tmp/setup_browser.out" 2>"$tmp/setup_browser.err" rc=$? set -e assert 'default (browser) invocation exits 0' bash -c "[ $rc -eq 0 ]" assert 'default (browser) invocation does NOT pass --no-browser' \ bash -c "! grep -qxF -- '--no-browser' \"$tmp/forge_login.args\"" # --- Live token + --headless: reuse without invoking forge_login.sh rm -f "$tmp/forge_login.args" cat >"$tmp/tokens/client-auth.json" <<'JSON' {"username":"sandbox-user","gitea_access_token":"live-token", "_forge_refresh_token":"r"} JSON chmod 0600 "$tmp/tokens/client-auth.json" cat >"$tmp/scripts/forge_auth.py" <<'EOF' #!/usr/bin/env python3 import sys sys.exit(0) EOF chmod +x "$tmp/scripts/forge_auth.py" set +e FORGE_SETUP_YES=1 bash "$tmp/scripts/setup.sh" --headless \ >"$tmp/setup_live.out" 2>"$tmp/setup_live.err" rc=$? set -e assert 'live + --headless exits 0' bash -c "[ $rc -eq 0 ]" assert 'live + --headless does NOT invoke forge_login.sh' \ bash -c "[ ! -f \"$tmp/forge_login.args\" ]" assert 'live + --headless reports reuse (on stderr, where logs belong)' \ grep -q 'reusing the stored session' "$tmp/setup_live.err" # --- Stale token, silent-refresh rescue: forge_login.sh still skipped rm -f "$tmp/forge_login.args" "$tmp/tokens/client-auth.json" \ "$tmp/forge_auth.calls" cat >"$tmp/tokens/client-auth.json" <<'JSON' {"username":"sandbox-user","_forge_refresh_token":"r", "gitea_access_token":""} JSON chmod 0600 "$tmp/tokens/client-auth.json" cat >"$tmp/scripts/forge_auth.py" <= 2 and sys.argv[1] == "status": sys.exit(1) if len(sys.argv) >= 2 and sys.argv[1] == "refresh": import json, time p = "$tmp/tokens/client-auth.json" d = json.load(open(p)) d["gitea_access_token"] = "refreshed-token" d["gitea_token_expires_at"] = time.time() + 3600 open(p, "w").write(json.dumps(d) + "\n") sys.exit(0) sys.exit(0) EOF chmod +x "$tmp/scripts/forge_auth.py" export FORGE_AUTH_CALLS_LOG="$tmp/forge_auth.calls" set +e FORGE_SETUP_YES=1 bash "$tmp/scripts/setup.sh" --headless \ >"$tmp/setup_refresh.out" 2>"$tmp/setup_refresh.err" rc=$? set -e assert 'silent-refresh rescue exits 0' bash -c "[ $rc -eq 0 ]" assert 'silent-refresh rescue skips forge_login.sh' \ bash -c "[ ! -f \"$tmp/forge_login.args\" ]" assert 'silent-refresh rescue called forge_auth.py refresh --force' \ grep -qF 'refresh --force' "$tmp/forge_auth.calls" assert 'silent-refresh rescue reports success (on stderr)' \ grep -q 'refreshed stored session without a browser' "$tmp/setup_refresh.err" # --- prompt_choice regression: non-tty branch must not contaminate stdout fake=$(bash -c " prompt_choice() { local msg=\"\$1\" default=\"\$2\" reply if [ \"\${FORGE_SETUP_YES:-0}\" = '1' ] || [ ! -t 0 ]; then printf '%s [%s] (auto: %s)\n' \"\$msg\" \"\$default\" \"\$default\" >&2 reply=\"\$default\" fi printf '%s' \"\$reply\" } FORGE_SETUP_YES=1 printf '%s' \"\$(prompt_choice 'Pick one?' 'R')\" ") assert 'prompt_choice non-tty branch returns only the default character' \ bash -c "[ \"$fake\" = 'R' ]" printf '\n%d pass / %d fail\n' "$pass" "$fail" [ "$fail" -eq 0 ]