266 lines
10 KiB
Bash
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 ]
|