From 126fbeb6d9c6500f9b455c706219222364ecb29c Mon Sep 17 00:00:00 2001 From: Hayden <154503486+groupthinking@users.noreply.github.com> Date: Wed, 28 Oct 2026 02:59:49 -0500 Subject: [PATCH 1/2] fix: exempt verified Dependabot updates from issue closer --- .github/workflows/require-issue-closer.yml | 12 ++ .../test_require_issue_closer_workflow.py | 126 ++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 tests/unit/test_require_issue_closer_workflow.py diff --git a/.github/workflows/require-issue-closer.yml b/.github/workflows/require-issue-closer.yml index b4a8b3428..7a9742901 100644 --- a/.github/workflows/require-issue-closer.yml +++ b/.github/workflows/require-issue-closer.yml @@ -19,8 +19,20 @@ jobs: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ github.event.pull_request.number }} REPO: ${{ github.repository }} + PR_AUTHOR: ${{ github.event.pull_request.user.login }} + HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} + HEAD_REF: ${{ github.event.pull_request.head.ref }} run: | set -euo pipefail + # Dependabot's same-repository dependency branches are managed updates. + # The reconciliation workflow also excludes these from issue drift. + if [ "$PR_AUTHOR" = 'dependabot[bot]' ] && + [ "$HEAD_REPO" = "$REPO" ] && + [[ "$HEAD_REF" == dependabot/* ]]; then + echo "Pass: verified same-repository Dependabot update." + exit 0 + fi + BODY=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}" --jq '.body // ""') mapfile -t ISSUES < <(printf '%s' "$BODY" | grep -oiE '\b(close[sd]?|fix(e[sd])?|resolve[sd]?)\s+#[0-9]+\b' | grep -oE '[0-9]+' | sort -u || true) diff --git a/tests/unit/test_require_issue_closer_workflow.py b/tests/unit/test_require_issue_closer_workflow.py new file mode 100644 index 000000000..0c6af3637 --- /dev/null +++ b/tests/unit/test_require_issue_closer_workflow.py @@ -0,0 +1,126 @@ +"""Run the issue-closer gate against bot and human PR metadata.""" + +from __future__ import annotations + +import os +import subprocess +from pathlib import Path + +import pytest +import yaml + +WORKFLOW = ( + Path(__file__).resolve().parents[2] / ".github/workflows/require-issue-closer.yml" +) + + +@pytest.mark.parametrize( + ("author", "head_repo", "head_ref", "body", "issue", "expected_pass"), + [ + ( + "dependabot[bot]", + "groupthinking/EventRelay", + "dependabot/npm_and_yarn/ip-address-10.7.4", + "", + "", + True, + ), + ( + "hayden", + "groupthinking/EventRelay", + "dependabot/npm_and_yarn/ip-address-10.7.4", + "", + "", + False, + ), + ( + "dependabot[bot]", + "another/EventRelay", + "dependabot/npm_and_yarn/ip-address-10.7.4", + "", + "", + False, + ), + ( + "dependabot[bot]", + "groupthinking/EventRelay", + "feature/no-issue", + "", + "", + False, + ), + ( + "hayden", + "groupthinking/EventRelay", + "feature/has-issue", + "Fixes #42", + '{"state":"open","is_pr":"no"}', + True, + ), + ( + "hayden", + "groupthinking/EventRelay", + "feature/closed-issue", + "Fixes #42", + '{"state":"closed","is_pr":"no"}', + False, + ), + ], +) +def test_issue_closer_only_exempts_verified_dependabot_updates( + tmp_path: Path, + author: str, + head_repo: str, + head_ref: str, + body: str, + issue: str, + expected_pass: bool, +) -> None: + workflow = yaml.safe_load(WORKFLOW.read_text()) + step = workflow["jobs"]["require-closing-keyword"]["steps"][0] + assert step["name"] == "Verify Fixes/Closes targets an open issue" + assert ( + workflow["jobs"]["require-closing-keyword"]["name"] + == "Closing keyword to open issue" + ) + + bin_dir = tmp_path / "bin" + bin_dir.mkdir() + gh = bin_dir / "gh" + gh.write_text( + "#!/bin/sh\n" + 'case "$2" in\n' + ' */pulls/*) printf "%s" "$GH_STUB_BODY" ;;\n' + ' */issues/*) printf "%s" "$GH_STUB_ISSUE" ;;\n' + " *) exit 91 ;;\n" + "esac\n" + ) + gh.chmod(0o755) + env = os.environ | { + "PATH": f"{bin_dir}{os.pathsep}{os.environ['PATH']}", + "GH_TOKEN": "test-only", + "PR_NUMBER": "42", + "REPO": "groupthinking/EventRelay", + "PR_AUTHOR": author, + "HEAD_REPO": head_repo, + "HEAD_REF": head_ref, + "GH_STUB_BODY": body, + "GH_STUB_ISSUE": issue, + } + result = subprocess.run( + ["bash", "-e", "-c", step["run"]], + cwd=tmp_path, + env=env, + text=True, + capture_output=True, + timeout=20, + ) + assert (result.returncode == 0) is expected_pass, result.stdout + result.stderr + if ( + author == "dependabot[bot]" + and head_repo == env["REPO"] + and head_ref.startswith("dependabot/") + ): + assert "verified same-repository Dependabot update" in result.stdout + elif not expected_pass: + assert "::error::" in result.stdout From 34bfcd0119458f0e5eebcdc6cb83be665f6a66f3 Mon Sep 17 00:00:00 2001 From: Hayden <154503486+groupthinking@users.noreply.github.com> Date: Wed, 28 Oct 2026 03:04:12 -0500 Subject: [PATCH 2/2] test: align license policy assertion with main --- tests/unit/test_dependency_review_workflow.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/test_dependency_review_workflow.py b/tests/unit/test_dependency_review_workflow.py index d096188f3..52c9c3633 100644 --- a/tests/unit/test_dependency_review_workflow.py +++ b/tests/unit/test_dependency_review_workflow.py @@ -115,6 +115,8 @@ def test_numpy_remains_subject_to_the_unchanged_license_policy() -> None: "MPL-2.0", "CC0-1.0", "0BSD", + "MIT-CMU", + "Unlicense", } exclusions = settings["allow-dependencies-licenses"].split(",") assert not any(