From a35a77f752933c50483fa1f3ef1bd2981869f32d Mon Sep 17 00:00:00 2001 From: cluade-reviewer-andresmgsl Date: Wed, 5 Aug 2026 13:11:33 +0000 Subject: [PATCH] fix(forgejo): a read failure names its verb too, and the tests assert the whole diagnostic (#192) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @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 --- changelog.d/192.md | 4 ++++ lib/forge-forgejo.sh | 11 ++++++++--- test/forge-backends.test.sh | 11 +++++++++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/changelog.d/192.md b/changelog.d/192.md index 916fc5e..8851646 100644 --- a/changelog.d/192.md +++ b/changelog.d/192.md @@ -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). diff --git a/lib/forge-forgejo.sh b/lib/forge-forgejo.sh index 066ce2c..0583f86 100644 --- a/lib/forge-forgejo.sh +++ b/lib/forge-forgejo.sh @@ -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 — a non-2xx is named, not +# forgejo_http_ok — 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 }')" diff --git a/test/forge-backends.test.sh b/test/forge-backends.test.sh index 908b5ab..fd44a84 100644 --- a/test/forge-backends.test.sh +++ b/test/forge-backends.test.sh @@ -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.