diff --git a/actions/setup/js/close_older_adapter.cjs b/actions/setup/js/close_older_adapter.cjs new file mode 100644 index 00000000000..02ec09d433a --- /dev/null +++ b/actions/setup/js/close_older_adapter.cjs @@ -0,0 +1,52 @@ +// @ts-check +/// + +const { closeOlderEntities } = require("./close_older_entities.cjs"); + +/** + * Close older issues/PRs using shared adapter wiring. + * @param {object} params + * @param {any} params.github + * @param {string} params.owner + * @param {string} params.repo + * @param {string} params.workflowId + * @param {{number: number, html_url: string}} params.newEntity + * @param {string} params.workflowName + * @param {string} params.runUrl + * @param {string | undefined} params.callerWorkflowId + * @param {string | undefined} params.closeOlderKey + * @param {string} params.entityType + * @param {string} params.entityTypePlural + * @param {(github: any, owner: string, repo: string, workflowId: string, excludeNumber: number, callerWorkflowId?: string, closeOlderKey?: string) => Promise>} params.searchOlderEntities + * @param {(params: any) => string} params.getCloseMessage + * @param {(github: any, owner: string, repo: string, entityId: number, message: string) => Promise} params.addComment + * @param {(github: any, owner: string, repo: string, entityId: number) => Promise} params.closeEntity + * @param {number} params.delayMs + * @param {(params: {newEntityUrl: string, newEntityNumber: number, workflowName: string, runUrl: string}) => any} params.messageParams + * @returns {Promise>} + */ +async function closeOlderWithAdapter(params) { + const result = await closeOlderEntities(params.github, params.owner, params.repo, params.workflowId, params.newEntity, params.workflowName, params.runUrl, { + entityType: params.entityType, + entityTypePlural: params.entityTypePlural, + // Use a closure so callerWorkflowId and closeOlderKey are forwarded to searchOlderEntities + // without going through the closeOlderEntities extraArgs mechanism (which appends + // excludeNumber last) + searchOlderEntities: (gh, o, r, wid, excludeNumber) => params.searchOlderEntities(gh, o, r, wid, excludeNumber, params.callerWorkflowId, params.closeOlderKey), + getCloseMessage: closeParams => params.getCloseMessage(params.messageParams(closeParams)), + addComment: params.addComment, + closeEntity: params.closeEntity, + delayMs: params.delayMs, + getEntityId: entity => entity.number, + getEntityUrl: entity => entity.html_url, + }); + + return result.map(item => ({ + number: item.number, + html_url: item.html_url || "", + })); +} + +module.exports = { + closeOlderWithAdapter, +}; diff --git a/actions/setup/js/close_older_adapter.test.cjs b/actions/setup/js/close_older_adapter.test.cjs new file mode 100644 index 00000000000..23f1dde99b7 --- /dev/null +++ b/actions/setup/js/close_older_adapter.test.cjs @@ -0,0 +1,107 @@ +// @ts-check + +import { describe, it, expect, beforeEach, vi } from "vitest"; +import { closeOlderWithAdapter } from "./close_older_adapter.cjs"; + +describe("close_older_adapter", () => { + beforeEach(() => { + vi.clearAllMocks(); + global.core = { + info: vi.fn(), + warning: vi.fn(), + error: vi.fn(), + }; + }); + + it("forwards callerWorkflowId and closeOlderKey to searchOlderEntities", async () => { + const github = {}; + const searchOlderEntities = vi.fn().mockResolvedValue([ + { + number: 12, + title: "Older issue", + html_url: "https://example/12", + }, + ]); + const getCloseMessage = vi.fn().mockReturnValue("close message"); + const addComment = vi.fn().mockResolvedValue({ id: 1 }); + const closeEntity = vi.fn().mockResolvedValue({ number: 12, html_url: "https://example/12" }); + + const result = await closeOlderWithAdapter({ + github, + owner: "owner", + repo: "repo", + workflowId: "workflow", + newEntity: { number: 55, html_url: "https://example/new" }, + workflowName: "Workflow", + runUrl: "https://example/run", + callerWorkflowId: "caller-id", + closeOlderKey: "close-key", + entityType: "issue", + entityTypePlural: "issues", + searchOlderEntities, + getCloseMessage, + addComment, + closeEntity, + delayMs: 1, + messageParams: params => ({ transformedEntityUrl: params.newEntityUrl, transformedEntityNumber: params.newEntityNumber }), + }); + + expect(searchOlderEntities).toHaveBeenCalledWith(github, "owner", "repo", "workflow", 55, "caller-id", "close-key"); + expect(result).toEqual([{ number: 12, html_url: "https://example/12" }]); + }); + + it("applies messageParams transformation before getCloseMessage", async () => { + const github = {}; + const getCloseMessage = vi.fn().mockReturnValue("close message"); + const addComment = vi.fn().mockResolvedValue({ id: 1 }); + const closeEntity = vi.fn().mockResolvedValue({ number: 12, html_url: "https://example/12" }); + + await closeOlderWithAdapter({ + github, + owner: "owner", + repo: "repo", + workflowId: "workflow", + newEntity: { number: 55, html_url: "https://example/new" }, + workflowName: "Workflow", + runUrl: "https://example/run", + callerWorkflowId: "caller-id", + closeOlderKey: "close-key", + entityType: "issue", + entityTypePlural: "issues", + searchOlderEntities: vi.fn().mockResolvedValue([{ number: 12, title: "Older issue", html_url: "https://example/12" }]), + getCloseMessage, + addComment, + closeEntity, + delayMs: 1, + messageParams: params => ({ transformedEntityUrl: params.newEntityUrl, transformedEntityNumber: params.newEntityNumber }), + }); + + expect(getCloseMessage).toHaveBeenCalledWith({ transformedEntityUrl: "https://example/new", transformedEntityNumber: 55 }); + expect(addComment).toHaveBeenCalledWith(github, "owner", "repo", 12, "close message"); + expect(closeEntity).toHaveBeenCalledWith(github, "owner", "repo", 12); + }); + + it("normalizes missing html_url in mapped result", async () => { + const result = await closeOlderWithAdapter({ + github: {}, + owner: "owner", + repo: "repo", + workflowId: "workflow", + newEntity: { number: 10, html_url: "https://example/new" }, + workflowName: "Workflow", + runUrl: "https://example/run", + callerWorkflowId: undefined, + closeOlderKey: undefined, + entityType: "issue", + entityTypePlural: "issues", + searchOlderEntities: vi.fn().mockResolvedValue([{ number: 22, title: "Older issue" }]), + getCloseMessage: vi.fn().mockReturnValue("close message"), + addComment: vi.fn().mockResolvedValue({ id: 1 }), + closeEntity: vi.fn().mockResolvedValue({ number: 22 }), + delayMs: 1, + messageParams: params => params, + }); + + expect(result).toEqual([{ number: 22, html_url: "" }]); + }); +}); diff --git a/actions/setup/js/close_older_issues.cjs b/actions/setup/js/close_older_issues.cjs index e7b51be8d98..957262f70ea 100644 --- a/actions/setup/js/close_older_issues.cjs +++ b/actions/setup/js/close_older_issues.cjs @@ -2,7 +2,8 @@ /// const { sanitizeContent } = require("./sanitize_content.cjs"); -const { closeOlderEntities, MAX_CLOSE_COUNT: SHARED_MAX_CLOSE_COUNT } = require("./close_older_entities.cjs"); +const { MAX_CLOSE_COUNT: SHARED_MAX_CLOSE_COUNT } = require("./close_older_entities.cjs"); +const { closeOlderWithAdapter } = require("./close_older_adapter.cjs"); const { buildMarkerSearchQuery, filterByMarker, logFilterSummary } = require("./close_older_search_helpers.cjs"); /** @@ -186,32 +187,30 @@ function getCloseOlderIssueMessage({ newIssueUrl, newIssueNumber, workflowName, * @returns {Promise>} List of closed issues */ async function closeOlderIssues(github, owner, repo, workflowId, newIssue, workflowName, runUrl, callerWorkflowId, closeOlderKey) { - const result = await closeOlderEntities(github, owner, repo, workflowId, newIssue, workflowName, runUrl, { + return closeOlderWithAdapter({ + github, + owner, + repo, + workflowId, + newEntity: newIssue, + workflowName, + runUrl, + callerWorkflowId, + closeOlderKey, entityType: "issue", entityTypePlural: "issues", - // Use a closure so callerWorkflowId and closeOlderKey are forwarded to searchOlderIssues - // without going through the closeOlderEntities extraArgs mechanism (which appends - // excludeNumber last) - searchOlderEntities: (gh, o, r, wid, excludeNumber) => searchOlderIssues(gh, o, r, wid, excludeNumber, callerWorkflowId, closeOlderKey), - getCloseMessage: params => - getCloseOlderIssueMessage({ - newIssueUrl: params.newEntityUrl, - newIssueNumber: params.newEntityNumber, - workflowName: params.workflowName, - runUrl: params.runUrl, - }), + searchOlderEntities: searchOlderIssues, + getCloseMessage: getCloseOlderIssueMessage, + messageParams: params => ({ + newIssueUrl: params.newEntityUrl, + newIssueNumber: params.newEntityNumber, + workflowName: params.workflowName, + runUrl: params.runUrl, + }), addComment: addIssueComment, closeEntity: closeIssueAsNotPlanned, delayMs: API_DELAY_MS, - getEntityId: entity => entity.number, - getEntityUrl: entity => entity.html_url, }); - - // Map to issue-specific return type - return result.map(item => ({ - number: item.number, - html_url: item.html_url || "", - })); } module.exports = { diff --git a/actions/setup/js/close_older_pull_requests.cjs b/actions/setup/js/close_older_pull_requests.cjs index 6293d12805a..a66eb406c84 100644 --- a/actions/setup/js/close_older_pull_requests.cjs +++ b/actions/setup/js/close_older_pull_requests.cjs @@ -2,7 +2,8 @@ /// const { sanitizeContent } = require("./sanitize_content.cjs"); -const { closeOlderEntities, MAX_CLOSE_COUNT: SHARED_MAX_CLOSE_COUNT } = require("./close_older_entities.cjs"); +const { MAX_CLOSE_COUNT: SHARED_MAX_CLOSE_COUNT } = require("./close_older_entities.cjs"); +const { closeOlderWithAdapter } = require("./close_older_adapter.cjs"); const { buildMarkerSearchQuery, filterByMarker, logFilterSummary } = require("./close_older_search_helpers.cjs"); /** @@ -185,32 +186,30 @@ function getCloseOlderPullRequestMessage({ newPullRequestUrl, newPullRequestNumb * @returns {Promise>} List of closed pull requests */ async function closeOlderPullRequests(github, owner, repo, workflowId, newPullRequest, workflowName, runUrl, callerWorkflowId, closeOlderKey) { - const result = await closeOlderEntities(github, owner, repo, workflowId, newPullRequest, workflowName, runUrl, { + return closeOlderWithAdapter({ + github, + owner, + repo, + workflowId, + newEntity: newPullRequest, + workflowName, + runUrl, + callerWorkflowId, + closeOlderKey, entityType: "pull request", entityTypePlural: "pull requests", - // Use a closure so callerWorkflowId and closeOlderKey are forwarded to searchOlderPullRequests - // without going through the closeOlderEntities extraArgs mechanism (which appends - // excludeNumber last) - searchOlderEntities: (gh, o, r, wid, excludeNumber) => searchOlderPullRequests(gh, o, r, wid, excludeNumber, callerWorkflowId, closeOlderKey), - getCloseMessage: params => - getCloseOlderPullRequestMessage({ - newPullRequestUrl: params.newEntityUrl, - newPullRequestNumber: params.newEntityNumber, - workflowName: params.workflowName, - runUrl: params.runUrl, - }), + searchOlderEntities: searchOlderPullRequests, + getCloseMessage: getCloseOlderPullRequestMessage, + messageParams: params => ({ + newPullRequestUrl: params.newEntityUrl, + newPullRequestNumber: params.newEntityNumber, + workflowName: params.workflowName, + runUrl: params.runUrl, + }), addComment: addPullRequestComment, closeEntity: closePullRequest, delayMs: API_DELAY_MS, - getEntityId: entity => entity.number, - getEntityUrl: entity => entity.html_url, }); - - // Map to pull-request-specific return type - return result.map(item => ({ - number: item.number, - html_url: item.html_url || "", - })); } module.exports = {