fix(forgejo): a read failure names its verb too, and the tests assert the whole diagnostic (#192)
All checks were successful
CI / test (pull_request) Successful in 1m35s
CI / release-exercise (pull_request) Successful in 10s
CI / self-guards (pull_request) Successful in 6s
CI / action-exercise (pull_request) Successful in 6s
CI / docs-sync-exercise (pull_request) Successful in 6s
labels / labels (pull_request) Successful in 8s

@codex-reviewer-andresmgsl caught a test that describes evidence it does not
collect — mine, and it is the class this PR is about.

Two cases were titled "naming the verb, path and status" and asserted only the
substring "500". The PUT boundary happened to satisfy the contract because
forgejo_write already passes "PUT $endpoint" to forgejo_http_ok. The GET
boundary did not: the diagnostic was `HTTP 500 from 'repos/o/r/issues/5'`, with
no verb at all — so a caller could not tell a failed READ from a failed WRITE
of the same path, and #192's acceptance criterion asks for exactly that
distinction.

Reads now pass "GET $endpoint" on both non-paginated and paginated paths, and
the two tests assert the complete expected diagnostic as one substring rather
than a status code that any failure would contain. Reverting the verb reds the
GET case.

Also, per the same review: the failed GET is asserted to write nothing, and the
failed PUT to have attempted exactly one write.

forge-backends 117/117 (was 115), test/run.sh 22/22 under jq 1.7 and jq 1.6,
shellcheck 0.10.0 and actionlint clean.

Refs #192
This commit is contained in:
cluade-reviewer-andresmgsl 2026-08-05 13:11:33 +00:00
parent 062e016a42
commit a35a77f752
3 changed files with 21 additions and 5 deletions

View file

@ -25,6 +25,10 @@
- A removal that changes nothing writes nothing, rather than replacing the set
with itself and opening a race for no state change (#192).
- Every failure diagnostic on the forgejo backend names the verb as well as the
path and the status. A read used to say `HTTP 500 from 'repos/…'`, which
cannot be told from a failed write of the same path (#192).
- The diagnostic names what was attempted and that it did not happen, instead
of blaming a missing label and telling the operator to bootstrap — a cause it
had not established (#192, #101).

View file

@ -123,7 +123,7 @@ forge_api() {
echo "forge_api: request failed: $endpoint" >&2
return 1
fi
forgejo_http_ok "$hdr" "$endpoint" || return 1
forgejo_http_ok "$hdr" "GET $endpoint" || return 1
if [ "$have_jq" = true ]; then jq -r "$jqexpr" <"$body"; else cat "$body"; fi
return 0
fi
@ -140,7 +140,7 @@ forge_api() {
echo "forge_api: request failed: $endpoint (page $page)" >&2
return 1
fi
forgejo_http_ok "$hdr" "$endpoint" || return 1
forgejo_http_ok "$hdr" "GET $endpoint" || return 1
# Re-read on EVERY page, not once (#4712). A board that changes size
# under the walk was invisible: page 1 declaring 4 and page 2 declaring
@ -223,9 +223,14 @@ EOF
printf '%s\n' "$total"
}
# forgejo_http_ok <header-file> <endpoint> — a non-2xx is named, not
# forgejo_http_ok <header-file> <verb-and-endpoint> — a non-2xx is named, not
# swallowed. gh exits non-zero on HTTP failure; curl does not without -f,
# and -f would throw away the body that says why.
# The second argument carries the VERB as well as the path — "GET repos/…",
# "PUT repos/…". #192's acceptance criterion is that a failure names the verb,
# the path and the status, and reads used to omit the verb: a caller reading
# `HTTP 500 from 'repos/o/r/issues/5'` could not tell a failed read from a
# failed write of the same path (@codex-reviewer-andresmgsl).
forgejo_http_ok() {
local hdr="$1" endpoint="$2" code
code="$(tr -d '\r' <"$hdr" | awk '/^HTTP\// { c = $2 } END { print c }')"

View file

@ -376,7 +376,11 @@ fail_get() {
REPO=o/r forge_issue_edit 5 --remove-label state:old
}
check "a failed current-label GET refuses, non-zero" 1 "" fail_get
check "...naming the verb, path and status" 1 "500" fail_get
check "...naming the verb, the path AND the status, in one diagnostic" 1 \
"HTTP 500 from 'GET repos/o/r/issues/5'" fail_get
get_write_count() { : >"$WRITES"; fail_get >/dev/null 2>&1; wc -l <"$WRITES"; }
check "...having written nothing: the read failed before any mutation" 0 "0" \
get_write_count
fail_put() {
FAKE_LABELS="$ROSTER" FAKE_LABEL_N=4 \
FAKE_ISSUE_LABELS='[{"name":"state:old","id":11},{"name":"scope:labels","id":13}]' \
@ -384,7 +388,10 @@ fail_put() {
REPO=o/r forge_issue_edit 5 --remove-label state:old
}
check "a failed replacement PUT refuses, non-zero" 1 "" fail_put
check "...naming the verb, path and status" 1 "500" fail_put
check "...naming the verb, the path AND the status, in one diagnostic" 1 \
"HTTP 500 from 'PUT repos/o/r/issues/5/labels'" fail_put
put_write_count() { : >"$WRITES"; fail_put >/dev/null 2>&1; wc -l <"$WRITES"; }
check "...having attempted only the one PUT" 0 "1" put_write_count
# An ADD-ONLY call keeps the additive POST (ceremony#128): a read-modify-write
# there clobbered a label set two seconds after a builder wrote it.