diff --git a/actions/setup/js/set_issue_field.cjs b/actions/setup/js/set_issue_field.cjs index 292c5534117..37d59d85c6d 100644 --- a/actions/setup/js/set_issue_field.cjs +++ b/actions/setup/js/set_issue_field.cjs @@ -47,7 +47,6 @@ async function fetchIssueFields(githubClient, owner, repo) { issueFields(first: 100) { nodes { __typename - ... on IssueField { id name } ... on IssueFieldText { id name } ... on IssueFieldNumber { id name } ... on IssueFieldDate { id name } @@ -61,7 +60,6 @@ async function fetchIssueFields(githubClient, owner, repo) { issueFields(first: 100) { nodes { __typename - ... on IssueField { id name } ... on IssueFieldText { id name } ... on IssueFieldNumber { id name } ... on IssueFieldDate { id name } diff --git a/actions/setup/js/set_issue_field.test.cjs b/actions/setup/js/set_issue_field.test.cjs index f841ec88abe..9d9bfc316fb 100644 --- a/actions/setup/js/set_issue_field.test.cjs +++ b/actions/setup/js/set_issue_field.test.cjs @@ -274,7 +274,7 @@ describe("set_issue_field (Handler Factory Architecture)", () => { expect(mockCore.error).not.toHaveBeenCalled(); }); - it("fetchIssueFields query uses concrete type fragments (no direct id/name on IssueFields union)", async () => { + it("fetchIssueFields query uses only concrete issue field type fragments", async () => { let capturedQuery = ""; mockGraphql.mockImplementation(query => { if (query.includes("repository(owner")) { @@ -295,8 +295,8 @@ describe("set_issue_field (Handler Factory Architecture)", () => { // Bare "id" or "name" would appear on their own line; inside a fragment they appear as "{ id name }" expect(capturedQuery).not.toMatch(/^\s+id\s*$/m); expect(capturedQuery).not.toMatch(/^\s+name\s*$/m); - // The query must include a base IssueField fragment to cover unknown/future field types - expect(capturedQuery).toContain("... on IssueField"); + // The query must not include the non-existent IssueField type fragment + expect(capturedQuery).not.toMatch(/\.\.\.\s+on\s+IssueField\s*\{/); // The query must include at least the concrete IssueFieldText fragment expect(capturedQuery).toContain("... on IssueFieldText"); // The query must include the IssueFieldSingleSelect fragment with options @@ -324,7 +324,7 @@ describe("set_issue_field (Handler Factory Architecture)", () => { expect(capturedQuery).not.toContain("... on User"); }); - it("fetchIssueFields filters out nodes missing id or name (null entries and unknown types without base fragment)", async () => { + it("fetchIssueFields filters out nodes missing id or name (null entries and unknown types)", async () => { mockGraphql.mockImplementation(query => { if (query.includes("issueFields")) { return Promise.resolve({ diff --git a/actions/setup/js/set_issue_field_api_query.integration.test.cjs b/actions/setup/js/set_issue_field_api_query.integration.test.cjs new file mode 100644 index 00000000000..da39dd94284 --- /dev/null +++ b/actions/setup/js/set_issue_field_api_query.integration.test.cjs @@ -0,0 +1,60 @@ +// @ts-check +import { describe, expect, it } from "vitest"; + +const ISSUE_FIELDS_DISCOVERY_QUERY = `query($owner: String!, $repo: String!) { + repository(owner: $owner, name: $repo) { + issueFields(first: 100) { + nodes { + __typename + ... on IssueFieldText { id name } + ... on IssueFieldNumber { id name } + ... on IssueFieldDate { id name } + ... on IssueFieldSingleSelect { id name options { id name } } + ... on IssueFieldMultiSelect { id name options { id name } } + } + } + owner { + __typename + ... on Organization { + issueFields(first: 100) { + nodes { + __typename + ... on IssueFieldText { id name } + ... on IssueFieldNumber { id name } + ... on IssueFieldDate { id name } + ... on IssueFieldSingleSelect { id name options { id name } } + ... on IssueFieldMultiSelect { id name options { id name } } + } + } + } + } + } +}`; + +describe("set_issue_field GraphQL discovery query integration", () => { + it("validates against live schema and excludes the removed IssueField fragment", async () => { + const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN; + if (!token) { + console.log("Skipping live GraphQL schema test - no GITHUB_TOKEN or GH_TOKEN available"); + return; + } + + const owner = process.env.GITHUB_REPOSITORY_OWNER || "github"; + const repo = process.env.GITHUB_REPOSITORY?.split("/")[1] || "gh-aw"; + + const { getOctokit } = await import("@actions/github"); + const octokit = getOctokit(token); + + try { + const result = await octokit.graphql(ISSUE_FIELDS_DISCOVERY_QUERY, { owner, repo }); + expect(result?.repository).toBeDefined(); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + if (message.includes("Blocked by DNS monitoring proxy")) { + console.log("Skipping live GraphQL schema test - api.github.com blocked by DNS monitoring proxy"); + return; + } + throw error; + } + }); +}); diff --git a/actions/setup/js/vitest.integration.config.mjs b/actions/setup/js/vitest.integration.config.mjs index 9d46fbe4197..c18b5d11338 100644 --- a/actions/setup/js/vitest.integration.config.mjs +++ b/actions/setup/js/vitest.integration.config.mjs @@ -4,7 +4,7 @@ export default defineConfig({ test: { environment: "node", globals: true, - include: ["frontmatter_hash_github_api.test.cjs"], + include: ["frontmatter_hash_github_api.test.cjs", "set_issue_field_api_query.integration.test.cjs"], testTimeout: 30000, hookTimeout: 10000, },