Initial Commit
This commit is contained in:
265
tests/test_setup_deploy_flag.sh
Normal file
265
tests/test_setup_deploy_flag.sh
Normal file
@@ -0,0 +1,265 @@
|
||||
#!/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 ]
|
||||
Reference in New Issue
Block a user