From 3885437f021003085f1d31188563af21c75cb03e Mon Sep 17 00:00:00 2001 From: cluade-reviewer-andresmgsl Date: Sun, 2 Aug 2026 18:49:28 +0000 Subject: [PATCH] test(forge): cover the open-pull REST gather at main() granularity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @codex-reviewer-andresmgsl's draft-stage finding: the closed/merged half of the term-3 replacement had an executable-path case, the open half did not. The 27 closes_references cases test the parser, not the `.body | @base64` -> base64 -d -> closes_references wiring around it. Both directions in one sweep so neither assertion passes vacuously: #50 is closed by an open PR and keeps its claim, #51 is closed by nothing and is reclaimed. `Closes #50` sits on the third line of the body, so the newline protection is non-vacuous — an @tsv-shaped regression that keeps only the first line reclaims #50 and reds the case. Verified by mutation: replacing the decode with `base64 -d | head -1` fails exactly "a claim closed by an open PR survives the base64 round trip" and nothing else; reverting restores 148/148. The clock is injected. INOW is a fixed 2033 epoch, so without ISSUEFLOW_NOW the subprocess reads its own wall clock, dates both claims in the future and keeps them on a negative age — green, and proving nothing. Caught while writing this case. Also renames the sibling assertion that still said "through GraphQL"; that gather has been REST since 5797b41. Refs #188 --- test/issueflow-reconcile.test.sh | 54 +++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/test/issueflow-reconcile.test.sh b/test/issueflow-reconcile.test.sh index 4812d2e..05d8d00 100644 --- a/test/issueflow-reconcile.test.sh +++ b/test/issueflow-reconcile.test.sh @@ -653,12 +653,64 @@ subprocess_out="$( subprocess_rc=$? check "executable sweep transitions merged Refs work" 0 "" \ test "$subprocess_rc" -eq 0 -check "...reaches the transition through GraphQL and the issue loop" 0 "" \ +check "...reaches the transition through the REST gather and the issue loop" 0 "" \ grep -qF '#40: merged Refs PR -> post-merge; claim released' <<<"$subprocess_out" check "...and performs the release edit from the executable path" 0 "" \ grep -qF -- 'issue edit 40 -R owner/repo --remove-assignee builder --remove-label claimed --add-label post-merge' \ "$ARRIVAL/fixtures/edits" +# -- the OPEN-pull gather, at main() granularity ---------------------------- +# The closed/merged half above proves one REST path; this proves the other, +# which is a DIFFERENT pipeline: `.body | @base64` -> base64 -d -> +# closes_references -> OPEN_PR_ISSUES. The 27 parser cases in +# test/closes_references.test.sh cannot reach it — they test the parser, not +# the encoding and wiring around it (#188). +# +# Both directions in ONE sweep, so neither assertion can pass vacuously: +# #50 IS closed by an open PR -> the claim is KEPT, no reclaim edit +# #51 is closed by nothing -> the claim is RECLAIMED +# A break anywhere in the pipeline reclaims #50 too, and the first check +# fails. A break that reclaims nothing fails the second. +# +# `Closes #50` sits on the THIRD line of the body on purpose. jq's @tsv +# escapes a newline to a literal backslash-n, so a line-oriented parser +# reading an @tsv-encoded body sees one line and drops everything after the +# first — with the declaration on line 3, that defect reclaims #50 and this +# case goes red. On line 1 it would pass either way, which is the definition +# of a vacuous test. +printf '%s\n' \ + '[{"number":500,"body":"## Summary\nSome prose about the work.\nCloses #50\n","merged_at":null}]' \ + >"$ARRIVAL/fixtures/repos_owner_repo_pulls_state_open_per_page_100.json" +printf '[]\n' >"$ARRIVAL/fixtures/repos_owner_repo_pulls_state_closed_per_page_100.json" +printf '[{"number":50},{"number":51}]\n' \ + >"$ARRIVAL/fixtures/repos_owner_repo_issues_state_open_per_page_100.json" +# Both claims are two hours quiet against a ONE-hour stale bound, so the +# reclaim clock has genuinely expired for whichever of them no open PR +# rescues. The clock is injected rather than real: INOW is a fixed epoch in +# 2033, so without ISSUEFLOW_NOW the subprocess reads its own wall clock, +# dates these claims in the future, and both survive on a negative age — +# which is a green test proving nothing. +for n in 50 51; do + jq -n --arg at "$(iso_at $((INOW - 7200)))" --argjson n "$n" \ + '{number:$n,user:{login:"triage-one"},created_at:$at,body:"- [x] built",labels:[{name:"claimed"}],assignees:[{login:"builder"}]}' \ + >"$ARRIVAL/fixtures/repos_owner_repo_issues_$n.json" + printf '[]\n' >"$ARRIVAL/fixtures/repos_owner_repo_issues_${n}_comments.json" +done +: >"$ARRIVAL/fixtures/edits" +open_pr_out="$( + env PATH="$ARRIVAL/stub:$PATH" GH_FIXTURES="$ARRIVAL/fixtures" \ + CEREMONY_FORGE=github ISSUEFLOW_NOW="$INOW" ISSUEFLOW_STALE_HOURS=1 \ + REPO=owner/repo LABELS_CONF="$ARRIVAL/labels.conf" \ + bash "$ROOT/actions/issueflow-reconcile/issueflow-reconcile.sh" 2>&1 +)" +check "the open-pull gather completes" 0 "" \ + grep -qF 'issueflow: reconciled.' <<<"$open_pr_out" +check "a claim closed by an open PR survives the base64 round trip" 1 "" \ + grep -qE 'issue edit 50 .*--remove-label claimed' "$ARRIVAL/fixtures/edits" +check "...while the claim no open PR closes is reclaimed in the same sweep" 0 "" \ + grep -qE 'issue edit 51 .*--remove-label claimed --add-label ready' \ + "$ARRIVAL/fixtures/edits" + # D2 preserved: only the deliberate stand-downs changed; a genuine failure on # the arrival path still kills the run loudly. : >"$ARRIVAL/fixtures/repos_owner_repo_issues_91.json.error"