Some checks failed
CI / test (pull_request) Failing after 3m13s
CI / release-exercise (pull_request) Successful in 11s
CI / self-guards (pull_request) Successful in 7s
CI / action-exercise (pull_request) Successful in 6s
CI / docs-sync-exercise (pull_request) Successful in 6s
Refs guard / refs-not-closing (pull_request) Has been skipped
labels / labels (pull_request) Successful in 8s
Three corrections from @codex-reviewer-andresmgsl's review of 935a813.
1. `api="${GITHUB_API_URL:-https://api.github.com}"` guessed GitHub when the
variable was absent — driven with a recording curl, it reported success
after POSTing to api.github.com from this forge. That is the "Never
'probably github'" rule, and the same unset-environment refusal #201 just
established for docs-sync. It now refuses before any request, and the test
asserts zero calls were made: refusing after a POST is not refusing.
2. The "never silenced with || true" invariant was still asserted by grepping
the gh line this port removed, so it passed on any REST implementation
including one that swallows a failed POST. It is rebound behaviourally: a
curl that dies at the transport must fail the step. Doing that revealed the
step failed with a bare exit 7 and no sentence, so it now names the failure
— owning the diagnostic is the whole point of the surrounding code.
3. docs/CONSUMERS.md and both caller comments still described `gh workflow
run` as the mechanism. They describe the REST dispatch now, and the manual
bootstrap command carries a forge-neutral curl form beside the gh one: a
cross-forge runbook that sends this forge to a missing binary is wrong even
where the prose around it is right.
Refs #205
182 lines
8 KiB
Bash
Executable file
182 lines
8 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# The sweep dispatch in .github/workflows/labels.yml (#205).
|
|
#
|
|
# This EXTRACTS the shipped step's `run:` script and EXECUTES it against a
|
|
# recording stub, rather than grepping the YAML for strings. A grep here would
|
|
# pass on a step that assembles a perfect request and never sends it — the
|
|
# shape of defect this repo keeps finding in its own tests. So every case
|
|
# asserts on what the step actually sent, or on what it actually did when the
|
|
# forge refused.
|
|
set -uo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
# shellcheck source=test/harness.sh
|
|
. "$ROOT/test/harness.sh"
|
|
|
|
WORKFLOW="$ROOT/.github/workflows/labels.yml"
|
|
TMP="$(mktemp -d)"
|
|
trap 'rm -rf "${TMP:?}"' EXIT
|
|
|
|
# --- the step under test, taken from the shipped workflow --------------------
|
|
STEP="$TMP/step.sh"
|
|
{
|
|
printf '%s\n' '#!/usr/bin/env bash' 'set -e'
|
|
yq -r '.jobs.trigger.steps[] | select(.name == "dispatch the sweep") | .run' "$WORKFLOW"
|
|
} >"$STEP"
|
|
chmod +x "$STEP"
|
|
|
|
step_extracted() { [ "$(wc -l <"$STEP")" -ge 10 ]; }
|
|
check "the step's script was extracted from the shipped workflow" 0 "" step_extracted
|
|
|
|
# --- stubs -------------------------------------------------------------------
|
|
# `curl` records every invocation and answers with the code the case wants. It
|
|
# parses only what the step actually passes, so a step that stopped sending
|
|
# `-d`, or changed the method, fails here rather than recording nothing.
|
|
make_curl() { # make_curl <http-code> <body>
|
|
cat >"$TMP/bin/curl" <<STUB
|
|
#!/usr/bin/env bash
|
|
method=GET; data=; url=; out=; write=
|
|
while [ \$# -gt 0 ]; do
|
|
case "\$1" in
|
|
-X) method="\$2"; shift 2 ;;
|
|
-d) data="\$2"; shift 2 ;;
|
|
-o) out="\$2"; shift 2 ;;
|
|
-w) write="\$2"; shift 2 ;;
|
|
-H) printf '%s\n' "HEADER \$2" >>"$TMP/calls"; shift 2 ;;
|
|
-fsS|-sS|-s) shift ;;
|
|
*) url="\$1"; shift ;;
|
|
esac
|
|
done
|
|
printf '%s\n' "METHOD \$method" "URL \$url" "DATA \$data" >>"$TMP/calls"
|
|
# the default-branch read is a plain GET whose stdout the step pipes to jq
|
|
if [ "\$method" = GET ]; then printf '{"default_branch":"trunk"}'; exit 0; fi
|
|
[ -n "\$out" ] && printf '%s' '$2' >"\$out"
|
|
[ -n "\$write" ] && printf '%s' '$1'
|
|
exit 0
|
|
STUB
|
|
chmod +x "$TMP/bin/curl"
|
|
}
|
|
|
|
run_step() { # run_step <http-code> <body> [env assignments...]
|
|
local code="$1" body="$2"
|
|
shift 2
|
|
rm -rf "${TMP:?}/bin"
|
|
mkdir -p "$TMP/bin"
|
|
: >"$TMP/calls"
|
|
make_curl "$code" "$body"
|
|
env PATH="$TMP/bin:$PATH" \
|
|
GITHUB_TOKEN=tok \
|
|
GITHUB_API_URL=https://forge.example/api/v1 \
|
|
GITHUB_REPOSITORY=owner/repo \
|
|
SWEEP_WORKFLOW=self-labels-sweep.yml \
|
|
"$@" \
|
|
"$STEP"
|
|
}
|
|
|
|
sent() { grep -h "^$1 " "$TMP/calls" | tail -1 | cut -d' ' -f2-; }
|
|
sent_field() { jq -r "$1" <<<"$(sent DATA)"; }
|
|
|
|
# --- the success path --------------------------------------------------------
|
|
check "a 204 dispatch succeeds, naming what it woke and where" 0 \
|
|
"sweep dispatched — self-labels-sweep.yml on main" \
|
|
run_step 204 "" DEFAULT_BRANCH=main
|
|
|
|
posted() { [ "$(sent METHOD)" = POST ]; }
|
|
check "...by POST, not GET" 0 "" posted
|
|
|
|
right_endpoint() {
|
|
[ "$(sent URL)" = \
|
|
"https://forge.example/api/v1/repos/owner/repo/actions/workflows/self-labels-sweep.yml/dispatches" ]
|
|
}
|
|
check "...to the dispatches endpoint of the workflow it was told to wake" 0 "" right_endpoint
|
|
|
|
ref_is_main() { [ "$(sent_field .ref)" = main ]; }
|
|
check "...carrying a ref, because REST has no default and refuses without one" 0 "" ref_is_main
|
|
|
|
bootstrap_is_string_no() { [ "$(sent_field '.inputs.bootstrap')" = no ]; }
|
|
check "...and bootstrap=no as a STRING input, not a bare flag" 0 "" bootstrap_is_string_no
|
|
|
|
bearer_sent() { grep -qF 'HEADER Authorization: Bearer tok' "$TMP/calls"; }
|
|
check "...under the bearer header both forges accept" 0 "" bearer_sent
|
|
|
|
# --- the ref it must NOT inherit ---------------------------------------------
|
|
# On pull_request_target GITHUB_REF_NAME is `<n>/merge`. A step that reaches
|
|
# for it dispatches at something that is not a branch — and the forge answers
|
|
# that with the opaque 500, so it would look like an outage.
|
|
check "a pull_request_target run still dispatches at the branch" 0 "" \
|
|
run_step 204 "" DEFAULT_BRANCH=main GITHUB_REF_NAME=203/merge
|
|
ref_is_not_a_merge_ref() { case "$(sent_field .ref)" in *merge*) return 1 ;; *) return 0 ;; esac; }
|
|
check "...never at its merge ref" 0 "" ref_is_not_a_merge_ref
|
|
|
|
# --- the fallback ------------------------------------------------------------
|
|
check "an absent default branch is read from the forge, not guessed" 0 "" \
|
|
run_step 204 "" DEFAULT_BRANCH=
|
|
ref_is_trunk() { [ "$(sent_field .ref)" = trunk ]; }
|
|
check "...and the dispatch uses what the read returned" 0 "" ref_is_trunk
|
|
|
|
# --- failure is loud, and the diagnostic is owned ----------------------------
|
|
# `gh workflow run` failing WAS the misconfiguration alarm. The port keeps that
|
|
# contract: a consumer missing the caller, its input, or `actions: write` must
|
|
# fail here rather than sweep silently never again.
|
|
check "an empty 500 fails the step — the alarm still rings" 1 \
|
|
"/self-labels-sweep.yml/dispatches (ref=main)" \
|
|
run_step 500 "" DEFAULT_BRANCH=main
|
|
check "...explaining Forgejo's EMPTY 500 rather than passing it through" 1 \
|
|
"empty 500 body from Forgejo means the workflow name or the ref did not resolve" \
|
|
run_step 500 "" DEFAULT_BRANCH=main
|
|
check "...and naming the consumer causes the alarm exists for" 1 "actions: write" \
|
|
run_step 500 "" DEFAULT_BRANCH=main
|
|
check "any non-204 fails, not only the statuses the API documents" 1 "forbidden" \
|
|
run_step 403 '{"message":"forbidden"}' DEFAULT_BRANCH=main
|
|
|
|
# --- the API root is not guessed ---------------------------------------------
|
|
# Defaulting an unset GITHUB_API_URL to api.github.com sent this forge's
|
|
# dispatch to GitHub and reported success (@codex-reviewer-andresmgsl). The
|
|
# teeth are the call count: refusing AFTER a request is not refusing.
|
|
run_step_no_api() {
|
|
rm -rf "${TMP:?}/bin"; mkdir -p "$TMP/bin"; : >"$TMP/calls"
|
|
make_curl 204 ""
|
|
env PATH="$TMP/bin:$PATH" GITHUB_TOKEN=tok GITHUB_REPOSITORY=owner/repo \
|
|
SWEEP_WORKFLOW=self-labels-sweep.yml DEFAULT_BRANCH=main "$STEP"
|
|
}
|
|
check "an unset GITHUB_API_URL refuses rather than guessing GitHub" 1 \
|
|
"GITHUB_API_URL is unset" run_step_no_api
|
|
no_calls_made() { [ ! -s "$TMP/calls" ]; }
|
|
check "...having made zero requests: refusing after a POST is not refusing" 0 "" no_calls_made
|
|
|
|
# --- a transport failure is not silence --------------------------------------
|
|
# The old `|| true` invariant was asserted by grepping the gh line this port
|
|
# removed, so it passed on any REST implementation including one that swallows
|
|
# a failed POST (@codex-reviewer-andresmgsl). Driven instead: curl itself exits
|
|
# non-zero, which `-w` cannot report because nothing is written.
|
|
make_failing_curl() {
|
|
printf '%s\n' '#!/usr/bin/env bash' 'echo "curl: (7) failed to connect" >&2' 'exit 7' \
|
|
>"$TMP/bin/curl"
|
|
chmod +x "$TMP/bin/curl"
|
|
}
|
|
run_step_curl_dies() {
|
|
rm -rf "${TMP:?}/bin"; mkdir -p "$TMP/bin"; : >"$TMP/calls"
|
|
make_failing_curl
|
|
env PATH="$TMP/bin:$PATH" GITHUB_TOKEN=tok \
|
|
GITHUB_API_URL=https://forge.example/api/v1 GITHUB_REPOSITORY=owner/repo \
|
|
SWEEP_WORKFLOW=self-labels-sweep.yml DEFAULT_BRANCH=main "$STEP"
|
|
}
|
|
check "a POST that never reaches the forge fails the step, and says so" 1 \
|
|
"never completed" run_step_curl_dies
|
|
|
|
# A code-aware guard alongside the behavioural one: no swallowing operator on
|
|
# the dispatch itself.
|
|
never_silenced() { sed 's/#.*//' "$STEP" | grep -qE '\|\|[[:space:]]*true'; }
|
|
check "...and the step carries no || true" 1 "" never_silenced
|
|
|
|
# --- what the port removed ---------------------------------------------------
|
|
# Strip comments first: the step's prose NAMES `gh workflow run` and
|
|
# CEREMONY_FORGE_CLIENT to explain what it replaced, so a raw grep asserts on
|
|
# the explanation instead of the code.
|
|
step_code() { sed 's/#.*//' "$STEP"; }
|
|
invokes_gh() { step_code | grep -qE '(^|[^[:alnum:]_])gh[[:space:]]'; }
|
|
decides_forge() { step_code | grep -qE 'GITHUB_SERVER_URL|CEREMONY_FORGE_CLIENT'; }
|
|
check "the step no longer INVOKES gh, its comments about it aside" 1 "" invokes_gh
|
|
check "...and no longer decides a forge, because REST needs no branch" 1 "" decides_forge
|
|
|
|
summary
|