269 lines
8.6 KiB
Bash
Executable File
269 lines
8.6 KiB
Bash
Executable File
#!/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" <<EOF
|
|
#!/usr/bin/env bash
|
|
printf '%s\n' "\$@" >"$tmp/forge_login.args"
|
|
mkdir -p "$tmp/tokens"
|
|
cat >"$tmp/tokens/client-auth.json" <<JSON
|
|
{"username":"sandbox-user","gitea_access_token":"t","_forge_refresh_token":"r"}
|
|
JSON
|
|
chmod 0600 "$tmp/tokens/client-auth.json"
|
|
echo "[forge_login stub] ok"
|
|
EOF
|
|
chmod +x "$tmp/scripts/forge_login.sh"
|
|
|
|
cat >"$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" <<EOF
|
|
#!/usr/bin/env bash
|
|
case "\$1" in
|
|
ls-remote) exit 0;;
|
|
clone)
|
|
dest=""
|
|
while [ \$# -gt 0 ]; do
|
|
case "\$1" in
|
|
--branch) shift; shift;;
|
|
-*) shift;;
|
|
*) if [ -z "\$dest" ]; then url="\$1"; shift; dest="\$1"; shift; else shift; fi;;
|
|
esac
|
|
done
|
|
mkdir -p "\$dest/.git"
|
|
exit 0;;
|
|
esac
|
|
exec /usr/bin/git "\$@"
|
|
EOF
|
|
chmod +x "$tmp/fakebin/git"
|
|
|
|
# Overriding FORGE_* explicitly is required: `just test` pre-loads the
|
|
# real repo's .env into this process, and common.sh::load_env honours
|
|
# existing env over the file.
|
|
export HOME="$tmp/home"
|
|
export PATH="$tmp/fakebin:/usr/bin:/bin"
|
|
export FSDGG_AUTH_STORE_PATH="$tmp/tokens/client-auth.json"
|
|
export FORGE_SETUP_YES=1
|
|
export FORGE_GITEA_URL="http://127.0.0.1:1"
|
|
export FORGE_GITEA_ORG="x"
|
|
export FORGE_GITEA_USERNAME="sandbox-user"
|
|
export FORGE_ORCHESTRATOR_REPO_URL="http://127.0.0.1:1/x/y.git"
|
|
export FORGE_ORCHESTRATOR_BRANCH=""
|
|
export FORGE_WORKSPACE_ROOT="."
|
|
export FSDGG_CLI_CLIENT_ID="sandbox-client"
|
|
export FSDGG_CLI_REDIRECT_URI="http://127.0.0.1:38111/callback"
|
|
|
|
# --- --headless + FORGE_SETUP_YES=1 + no stored session: guard fires
|
|
set +e
|
|
FORGE_SETUP_YES=1 bash "$tmp/scripts/setup.sh" --headless \
|
|
>"$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" <<EOF
|
|
#!/usr/bin/env python3
|
|
import sys, os
|
|
with open(os.environ["FORGE_AUTH_CALLS_LOG"], "a") as f:
|
|
f.write(" ".join(sys.argv[1:]) + "\n")
|
|
if len(sys.argv) >= 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 ]
|