diff --git a/.github/workflows/release-on-resolved-issue.yml b/.github/workflows/release-on-resolved-issue.yml index 4db6927..48104fa 100644 --- a/.github/workflows/release-on-resolved-issue.yml +++ b/.github/workflows/release-on-resolved-issue.yml @@ -40,7 +40,7 @@ jobs: shell: bash run: | set -euo pipefail - query='query($owner:String!, $repo:String!, $sha:String!) { repository(owner:$owner, name:$repo) { object(expression:$sha) { ... on Commit { associatedPullRequests(first:100) { pageInfo { hasNextPage } nodes { number mergeCommit { oid } labels(first:20) { pageInfo { hasNextPage } nodes { name } } closingIssuesReferences(first:20) { pageInfo { hasNextPage } nodes { number repository { nameWithOwner } labels(first:30) { pageInfo { hasNextPage } nodes { name } } } } } } } } } } }' + query="$(< scripts/release_eligibility.graphql)" result="$(gh api graphql -f query="$query" -f owner="${GITHUB_REPOSITORY_OWNER}" -f repo="${GITHUB_REPOSITORY#*/}" -f sha="$TESTED_SHA")" repository="$(jq -ce '{pullRequests: (.data.repository.object.associatedPullRequests // error("tested SHA is not a commit"))}' <<< "$result")" if outcome="$(python scripts/release_eligibility.py --tested-sha "$TESTED_SHA" --repository "$GITHUB_REPOSITORY" --pull-requests-json "$repository")"; then diff --git a/scripts/release_eligibility.graphql b/scripts/release_eligibility.graphql new file mode 100644 index 0000000..9cf1fdf --- /dev/null +++ b/scripts/release_eligibility.graphql @@ -0,0 +1,46 @@ +query($owner: String!, $repo: String!, $sha: String!) { + repository(owner: $owner, name: $repo) { + object(expression: $sha) { + ... on Commit { + associatedPullRequests(first: 100) { + pageInfo { + hasNextPage + } + nodes { + number + mergeCommit { + oid + } + labels(first: 20) { + pageInfo { + hasNextPage + } + nodes { + name + } + } + closingIssuesReferences(first: 20) { + pageInfo { + hasNextPage + } + nodes { + number + repository { + nameWithOwner + } + labels(first: 30) { + pageInfo { + hasNextPage + } + nodes { + name + } + } + } + } + } + } + } + } + } +} diff --git a/tests/test_release_eligibility_query.py b/tests/test_release_eligibility_query.py new file mode 100644 index 0000000..62250f6 --- /dev/null +++ b/tests/test_release_eligibility_query.py @@ -0,0 +1,43 @@ +from pathlib import Path + + +QUERY = Path(__file__).parents[1] / "scripts" / "release_eligibility.graphql" +WORKFLOW = Path(__file__).parents[1] / ".github" / "workflows" / "release-on-resolved-issue.yml" + + +def _balanced_graphql_delimiters(source: str) -> bool: + """Check the structural delimiters that GitHub's GraphQL parser requires. + + This query contains no GraphQL string literals or comments, so a small + delimiter check catches malformed edits without adding a runtime-only + GraphQL parser dependency to the CLI test environment. + """ + pairs = {"}": "{", ")": "("} + stack: list[str] = [] + for character in source: + if character in "{(": + stack.append(character) + elif character in pairs: + if not stack or stack.pop() != pairs[character]: + return False + return not stack + + +def test_release_eligibility_query_is_structurally_valid_and_wired_to_workflow(): + query = QUERY.read_text() + + assert _balanced_graphql_delimiters(query) + assert "query($owner: String!, $repo: String!, $sha: String!)" in query + assert "... on Commit" in query + assert "associatedPullRequests(first: 100)" in query + assert "closingIssuesReferences(first: 20)" in query + assert "query=\"$(< scripts/release_eligibility.graphql)\"" in WORKFLOW.read_text() + + +def test_release_eligibility_query_requests_every_fail_closed_page_boundary(): + query = QUERY.read_text() + + # The classifier rejects a truncated pull-request, label, or closing-issue + # connection. Keep every corresponding pageInfo request in the query. + assert query.count("pageInfo {") == 4 + assert query.count("hasNextPage") == 4