diff --git a/actions/labels-reconcile/labels-reconcile.sh b/actions/labels-reconcile/labels-reconcile.sh index 252b885..04c84ef 100755 --- a/actions/labels-reconcile/labels-reconcile.sh +++ b/actions/labels-reconcile/labels-reconcile.sh @@ -711,7 +711,10 @@ reconcile_pr() { # $1 = PR number; relies on the globals set from its fetch { jq -r '.created_at' <<<"$PR_JSON" jq -r '.[].submitted_at // empty' <<<"$REVIEWS_JSON" - forge_pr_activity "$n" 2>/dev/null || true + # Non-fatal degrade (pre-#188 same edge), but do NOT swallow stderr — + # forge_api names failures loudly, and hiding them re-opens this issue's + # silent-green class (#4879 / #101 D5). + forge_pr_activity "$n" || true } | sort | tail -n1 )" last_activity_epoch="$(date -d "$last_activity" +%s)" diff --git a/test/forge-backends.test.sh b/test/forge-backends.test.sh index 7bbbfc1..e41b120 100644 --- a/test/forge-backends.test.sh +++ b/test/forge-backends.test.sh @@ -386,6 +386,72 @@ check "...and never routes through issue edit --add-label" 1 "" \ grep -q 'issue edit' "$gh_calls" check "github request_reviewer posts the reviewer" 0 "" \ grep -q 'api repos/o/r/pulls/9/requested_reviewers -f reviewers\[\]=danmt' "$gh_calls" + +# --- term-5 pins for the batch verbs (codex 1566) ------------------------- +# The forgejo twins have hermetic coverage below; these pin that the github +# twins stay 1:1 extractions of the pre-port endpoints, not silent rewrites. +: >"$gh_calls" +# shellcheck disable=SC2317 # invoked indirectly, by the github verbs +gh() { + printf '%s\n' "$*" >>"$gh_calls" + if [ "$1" = api ]; then + shift + local jqexpr="" endpoint="" + while [ $# -gt 0 ]; do + case "$1" in + --jq) jqexpr="$2"; shift ;; + --paginate) ;; + -*) ;; + *) [ -n "$endpoint" ] || endpoint="$1" ;; + esac + shift + done + local body='[]' + case "$endpoint" in + *'/issues/'*'/timeline'*) + body='[{"event":"labeled","actor":{"login":"alice"},"label":{"name":"ready"},"created_at":"2026-08-01T09:00:00Z"}]' + ;; + *'/issues/'*'/comments'*) + body='[{"created_at":"2026-08-01T10:00:00Z"}]' + ;; + *'/pulls/'*'/comments'*) + body='[{"created_at":"2026-08-01T10:30:00Z"}]' + ;; + *'/pulls/'*'/commits'*) + body='[{"commit":{"committer":{"date":"2026-08-01T11:00:00Z"}}}]' + ;; + esac + if [ -n "$jqexpr" ]; then jq -r "$jqexpr" <<<"$body"; else printf '%s\n' "$body"; fi + return 0 + fi + return 0 +} +gh_tl="$( + forge_select github + # shellcheck disable=SC2030 # scoping REPO to this subshell is the point + REPO=o/r + forge_timeline 42 +)" +check "github forge_timeline paginates the issue timeline endpoint" 0 "" \ + grep -qE 'api --paginate repos/o/r/issues/42/timeline|api repos/o/r/issues/42/timeline' "$gh_calls" +check "github forge_timeline is a pass-through of the GitHub event shape" 0 "" \ + jq -e '.[0].event == "labeled" and .[0].actor.login == "alice"' <<<"$gh_tl" >/dev/null +: >"$gh_calls" +gh_act="$( + forge_select github + # shellcheck disable=SC2030 # scoping REPO to this subshell is the point + REPO=o/r + forge_pr_activity 9 | sort +)" +check "github forge_pr_activity hits issue comments" 0 "" \ + grep -q 'repos/o/r/issues/9/comments' "$gh_calls" +check "github forge_pr_activity hits the flat /pulls/{n}/comments endpoint" 0 "" \ + grep -q 'repos/o/r/pulls/9/comments' "$gh_calls" +check "github forge_pr_activity hits commits" 0 "" \ + grep -q 'repos/o/r/pulls/9/commits' "$gh_calls" +check "github forge_pr_activity emits all three timestamp sources" 0 "" \ + test "$(printf '%s\n' "$gh_act")" = "$(printf '%s\n' '2026-08-01T10:00:00Z' '2026-08-01T10:30:00Z' '2026-08-01T11:00:00Z')" + unset -f gh . "$ROOT/lib/forge-forgejo.sh"