Files
welcome-to-codevalet-as-a-p…/tests/test_setup_deploy_flag.sh
FanaticPythoner (Nathan Trudeau) 0c159e91fb Initial Commit
2026-04-27 15:56:43 -04:00

266 lines
10 KiB
Bash

#!/usr/bin/env bash
#
# scripts/setup.sh --deploy flag + scripts/next_steps.sh --manifest /
# --recipe flags. Runs hermetically against a sandboxed $HOME with
# stubbed scripts and a stubbed orchestrator checkout carrying both
# contributor_setup_steps.json and operator_setup_steps.json.
#
# Operator welcome scaffold dispatch contract:
# - `just setup` (no --deploy) ALWAYS uses operator_setup_steps.json
# and operator-setup. It STOPS after the clone (prints the plan as
# informational text; does NOT prompt; does NOT invoke any recipe).
# Operators who want the handoff use `just deploy`.
# - `just setup --deploy` (== `just deploy`) ALWAYS uses operator_*
# and prompts [Y/n] (default Y) before auto-handing off to
# `just operator-setup` inside the orchestrator checkout.
# - The contributor manifest never appears in the operator scaffold's
# dispatch surface; the bug fixed here was setup.sh defaulting to
# contributor_setup_steps.json when --deploy was absent.
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
}
# -- syntax ---------------------------------------------------------------
assert 'setup.sh parses as valid bash' bash -n "$root/scripts/setup.sh"
assert 'next_steps.sh parses as valid bash' bash -n "$root/scripts/next_steps.sh"
# -- --help documents --deploy and --manifest/--recipe --------------------
# NOTE: capture help output to a file; inlining it into `bash -c` would
# expose backticks in the text (e.g. `just operator-setup`) to command
# substitution. Use files or here-strings instead.
setup_help_file="$(mktemp)"
bash "$root/scripts/setup.sh" --help >"$setup_help_file" 2>&1
assert 'setup --help documents --deploy' \
grep -q -- '--deploy' "$setup_help_file"
assert 'setup --help mentions operator-setup' \
grep -q 'operator-setup' "$setup_help_file"
assert 'setup --help mentions operator_setup_steps.json' \
grep -q 'operator_setup_steps.json' "$setup_help_file"
rm -f "$setup_help_file"
next_help_file="$(mktemp)"
bash "$root/scripts/next_steps.sh" --help >"$next_help_file" 2>&1
assert 'next_steps --help documents --manifest' \
grep -q -- '--manifest' "$next_help_file"
assert 'next_steps --help documents --recipe' \
grep -q -- '--recipe' "$next_help_file"
rm -f "$next_help_file"
# -- sandbox --------------------------------------------------------------
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
mkdir -p "$tmp/scripts" "$tmp/home" "$tmp/tokens" "$tmp/workspace"
touch "$tmp/Justfile"
cp "$root/scripts/setup.sh" "$tmp/scripts/setup.sh"
cp "$root/scripts/next_steps.sh" "$tmp/scripts/next_steps.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="./workspace"
FSDGG_CLI_CLIENT_ID="sandbox-client"
FSDGG_CLI_REDIRECT_URI="http://127.0.0.1:38111/callback"
EOF
cp "$tmp/.env.example" "$tmp/.env"
# stub doctor/login/helper
for name in doctor.sh forge_login.sh install-git-credential-helper.sh; do
cat >"$tmp/scripts/$name" <<EOF
#!/usr/bin/env bash
echo "[$name stub] ok"
EOF
chmod +x "$tmp/scripts/$name"
done
# forge_auth.py stub: live token after first status call
cat >"$tmp/scripts/forge_auth.py" <<'EOF'
#!/usr/bin/env python3
import sys
sys.exit(0)
EOF
chmod +x "$tmp/scripts/forge_auth.py"
# curl stub
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"
# git stub: ls-remote OK; clone → seed the fake orchestrator with both manifests
cat >"$tmp/fakebin/git" <<EOF
#!/usr/bin/env bash
case "\$1" in
ls-remote) exit 0;;
clone)
dest=""
url=""
while [ \$# -gt 0 ]; do
case "\$1" in
--branch) shift; shift;;
-*) shift;;
*)
if [ -z "\$url" ]; then url="\$1"; shift
else dest="\$1"; shift
fi;;
esac
done
mkdir -p "\$dest/.git" "\$dest/scripts"
# Contributor manifest (plain echo of one step)
cat >"\$dest/scripts/contributor_setup_steps.json" <<'JSON'
{"schema_version":1,"steps":[{"id":"noop","title":"Contributor noop","cmd":["true"]}]}
JSON
# Operator manifest (plain echo of one step)
cat >"\$dest/scripts/operator_setup_steps.json" <<'JSON'
{"schema_version":1,"steps":[{"id":"op-step","title":"Operator noop","cmd":["true"]}]}
JSON
# Fake Justfile exposing both recipes.
# The test records invocations to verify the selected recipe.
cat >"\$dest/Justfile" <<'JUST'
contributor-setup *args:
@echo "[stub-orchestrator] called: contributor-setup \$@" >>.invocations
operator-setup *args:
@echo "[stub-orchestrator] called: operator-setup \$@" >>.invocations
JUST
exit 0;;
esac
exec /usr/bin/git "\$@"
EOF
chmod +x "$tmp/fakebin/git"
# setup.sh deploy path calls `exec bash next_steps.sh --run --manifest ... --recipe ...`.
# That path reaches `exec just <recipe>`. The stub keeps the test
# deterministic and avoids a dependency on the sandboxed orchestrator.
cat >"$tmp/fakebin/just" <<EOF
#!/usr/bin/env bash
# Log the invocation with cwd to confirm the exec target directory.
printf 'just %s (cwd=%s)\n' "\$*" "\$(pwd)" >>"$tmp/just.calls"
exit 0
EOF
chmod +x "$tmp/fakebin/just"
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="$tmp/workspace"
export FSDGG_CLI_CLIENT_ID="sandbox-client"
export FSDGG_CLI_REDIRECT_URI="http://127.0.0.1:38111/callback"
# Live stored token so setup.sh skips the login step (FORGE_SETUP_YES+headless
# would otherwise trip the guard).
mkdir -p "$tmp/tokens"
cat >"$tmp/tokens/client-auth.json" <<'JSON'
{"username":"sandbox-user","gitea_access_token":"live","_forge_refresh_token":"r",
"gitea_token_expires_at":32503680000}
JSON
chmod 0600 "$tmp/tokens/client-auth.json"
# -- A) default path: operator manifest, no auto-run --------------------
# Without --deploy the operator scaffold ALWAYS uses the operator
# manifest and STOPS after the clone. The contributor manifest must
# never be referenced; no recipe must be invoked.
rm -f "$tmp/just.calls"
set +e
FORGE_SETUP_YES=1 bash "$tmp/scripts/setup.sh" --headless \
>"$tmp/out_default.out" 2>"$tmp/out_default.err"
rc=$?
set -e
assert 'default (no --deploy) exits 0' bash -c "[ $rc -eq 0 ]"
assert 'default path renders operator_setup_steps.json' \
grep -qF 'operator_setup_steps.json' "$tmp/out_default.err"
assert 'default path mentions just operator-setup in the handoff hint' \
grep -qF 'just operator-setup' "$tmp/out_default.err"
assert 'default path does NOT reference contributor_setup_steps.json' \
bash -c "! grep -qF 'contributor_setup_steps.json' \"$tmp/out_default.err\""
assert 'default path does NOT mention contributor-setup' \
bash -c "! grep -qF 'contributor-setup' \"$tmp/out_default.err\""
assert 'default path does NOT prompt for an operator-setup handoff' \
bash -c "! grep -qF 'Hand off to' \"$tmp/out_default.err\""
assert 'default path does NOT invoke any just recipe (no auto-run)' \
bash -c "! [ -f \"$tmp/just.calls\" ]"
# -- B) --deploy path: operator manifest, default-Y prompt, hands off ---
# Under FORGE_SETUP_YES=1 the [Y/n] prompt's default Y is taken, so the
# stubbed `just operator-setup` is invoked inside the orchestrator cwd.
rm -f "$tmp/just.calls"
set +e
FORGE_SETUP_YES=1 bash "$tmp/scripts/setup.sh" --headless --deploy \
>"$tmp/out_deploy.out" 2>"$tmp/out_deploy.err"
rc=$?
set -e
assert '--deploy exits 0' bash -c "[ $rc -eq 0 ]"
assert '--deploy renders operator_setup_steps.json' \
grep -qF 'operator_setup_steps.json' "$tmp/out_deploy.err"
assert '--deploy prompt names operator-setup' \
grep -qF 'operator-setup' "$tmp/out_deploy.err"
assert '--deploy prompt is [Y/n] (default Y, handoff is the default)' \
grep -qE 'Hand off to.*\[Y/n\]' "$tmp/out_deploy.err"
assert '--deploy invokes just operator-setup (FORGE_SETUP_YES=1 takes the default Y)' \
bash -c "[ -f \"$tmp/just.calls\" ] && grep -qE '^just operator-setup' \"$tmp/just.calls\""
assert '--deploy does NOT invoke just contributor-setup' \
bash -c "! { [ -f \"$tmp/just.calls\" ] && grep -qE '^just contributor-setup' \"$tmp/just.calls\"; }"
assert '--deploy hands off in the cloned orchestrator cwd (basename of FORGE_ORCHESTRATOR_REPO_URL)' \
grep -qF "(cwd=$tmp/workspace/y)" "$tmp/just.calls"
# -- C) run-mode wiring: next_steps.sh --run --manifest --recipe -----
# Invoke next_steps.sh directly with --run to prove the runner execs
# `just operator-setup` (not `just contributor-setup`) when --recipe is set.
rm -f "$tmp/just.calls"
set +e
bash "$tmp/scripts/next_steps.sh" --run --manifest operator_setup_steps.json --recipe operator-setup \
>"$tmp/out_run_deploy.out" 2>"$tmp/out_run_deploy.err"
rc=$?
set -e
assert 'next_steps --run --recipe operator-setup exits 0' bash -c "[ $rc -eq 0 ]"
assert 'next_steps --run invoked `just operator-setup`' \
grep -q '^just operator-setup' "$tmp/just.calls"
assert 'next_steps --run did NOT invoke `just contributor-setup`' \
bash -c "! grep -q '^just contributor-setup' \"$tmp/just.calls\""
# -- D) print mode shows updated skip-autorun hints ---------------------
set +e
bash "$tmp/scripts/next_steps.sh" --manifest operator_setup_steps.json --recipe operator-setup \
>"$tmp/out_print.out" 2>"$tmp/out_print.err"
rc=$?
set -e
assert 'next_steps print mode exits 0' bash -c "[ $rc -eq 0 ]"
assert 'print hint mentions operator-setup recipe' \
grep -q "just operator-setup" "$tmp/out_print.err"
assert 'print hint mentions --recipe flag' \
grep -q "run-next-steps --manifest operator_setup_steps.json --recipe operator-setup" "$tmp/out_print.err"
printf '\n%d pass / %d fail\n' "$pass" "$fail"
[ "$fail" -eq 0 ]