diff --git a/actions/setup/js/check_stop_time.cjs b/actions/setup/js/check_stop_time.cjs index 4e86da0f8a..96dff917c9 100644 --- a/actions/setup/js/check_stop_time.cjs +++ b/actions/setup/js/check_stop_time.cjs @@ -1,9 +1,21 @@ // @ts-check /// +/** + * Validates date format and returns true if valid + * @param {Date} date - Date to validate + * @returns {boolean} True if date is valid + */ +function isValidDate(date) { + return !isNaN(date.getTime()); +} + +/** + * Checks if workflow execution should stop based on configured stop time + * @returns {Promise} + */ async function main() { - const stopTime = process.env.GH_AW_STOP_TIME; - const workflowName = process.env.GH_AW_WORKFLOW_NAME; + const { GH_AW_STOP_TIME: stopTime, GH_AW_WORKFLOW_NAME: workflowName } = process.env; if (!stopTime) { core.setFailed("Configuration error: GH_AW_STOP_TIME not specified."); @@ -17,10 +29,9 @@ async function main() { core.info(`Checking stop-time limit: ${stopTime}`); - // Parse the stop time (format: "YYYY-MM-DD HH:MM:SS") const stopTimeDate = new Date(stopTime); - if (isNaN(stopTimeDate.getTime())) { + if (!isValidDate(stopTimeDate)) { core.setFailed(`Invalid stop-time format: ${stopTime}. Expected format: YYYY-MM-DD HH:MM:SS`); return; } diff --git a/actions/setup/js/check_stop_time.test.cjs b/actions/setup/js/check_stop_time.test.cjs index bfa82031f4..074c833aca 100644 --- a/actions/setup/js/check_stop_time.test.cjs +++ b/actions/setup/js/check_stop_time.test.cjs @@ -1,101 +1,141 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import fs from "fs"; import path from "path"; + const mockCore = { - debug: vi.fn(), - info: vi.fn(), - notice: vi.fn(), - warning: vi.fn(), - error: vi.fn(), - setFailed: vi.fn(), - setOutput: vi.fn(), - exportVariable: vi.fn(), - setSecret: vi.fn(), - setCancelled: vi.fn(), - setError: vi.fn(), - getInput: vi.fn(), - getBooleanInput: vi.fn(), - getMultilineInput: vi.fn(), - getState: vi.fn(), - saveState: vi.fn(), - startGroup: vi.fn(), - endGroup: vi.fn(), - group: vi.fn(), - addPath: vi.fn(), - setCommandEcho: vi.fn(), - isDebug: vi.fn().mockReturnValue(!1), - getIDToken: vi.fn(), - toPlatformPath: vi.fn(), - toPosixPath: vi.fn(), - toWin32Path: vi.fn(), - summary: { addRaw: vi.fn().mockReturnThis(), write: vi.fn().mockResolvedValue() }, - }, - mockGithub = { rest: { actions: { listRepoWorkflows: vi.fn(), disableWorkflow: vi.fn() } } }, - mockContext = { repo: { owner: "testowner", repo: "testrepo" } }; -((global.core = mockCore), - (global.github = mockGithub), - (global.context = mockContext), - describe("check_stop_time.cjs", () => { - let checkStopTimeScript, originalEnv; - (beforeEach(() => { - (vi.clearAllMocks(), (originalEnv = { GH_AW_STOP_TIME: process.env.GH_AW_STOP_TIME, GH_AW_WORKFLOW_NAME: process.env.GH_AW_WORKFLOW_NAME })); - const scriptPath = path.join(process.cwd(), "check_stop_time.cjs"); - checkStopTimeScript = fs.readFileSync(scriptPath, "utf8"); - }), - afterEach(() => { - (void 0 !== originalEnv.GH_AW_STOP_TIME ? (process.env.GH_AW_STOP_TIME = originalEnv.GH_AW_STOP_TIME) : delete process.env.GH_AW_STOP_TIME, - void 0 !== originalEnv.GH_AW_WORKFLOW_NAME ? (process.env.GH_AW_WORKFLOW_NAME = originalEnv.GH_AW_WORKFLOW_NAME) : delete process.env.GH_AW_WORKFLOW_NAME); - }), - describe("when stop time is not configured", () => { - (it("should fail if GH_AW_STOP_TIME is not set", async () => { - (delete process.env.GH_AW_STOP_TIME, - (process.env.GH_AW_WORKFLOW_NAME = "test-workflow"), - await eval(`(async () => { ${checkStopTimeScript}; await main(); })()`), - expect(mockCore.setFailed).toHaveBeenCalledWith(expect.stringContaining("GH_AW_STOP_TIME not specified")), - expect(mockCore.setOutput).not.toHaveBeenCalled()); - }), - it("should fail if GH_AW_WORKFLOW_NAME is not set", async () => { - ((process.env.GH_AW_STOP_TIME = "2025-12-31 23:59:59"), - delete process.env.GH_AW_WORKFLOW_NAME, - await eval(`(async () => { ${checkStopTimeScript}; await main(); })()`), - expect(mockCore.setFailed).toHaveBeenCalledWith(expect.stringContaining("GH_AW_WORKFLOW_NAME not specified")), - expect(mockCore.setOutput).not.toHaveBeenCalled()); - })); - }), - describe("when stop time format is invalid", () => { - it("should fail with error for invalid format", async () => { - ((process.env.GH_AW_STOP_TIME = "invalid-date"), - (process.env.GH_AW_WORKFLOW_NAME = "test-workflow"), - await eval(`(async () => { ${checkStopTimeScript}; await main(); })()`), - expect(mockCore.setFailed).toHaveBeenCalledWith(expect.stringContaining("Invalid stop-time format")), - expect(mockCore.setOutput).not.toHaveBeenCalled()); - }); - }), - describe("when stop time is in the future", () => { - it("should allow execution", async () => { - const futureDate = new Date(); - futureDate.setFullYear(futureDate.getFullYear() + 1); - const stopTime = futureDate.toISOString().replace("T", " ").substring(0, 19); - ((process.env.GH_AW_STOP_TIME = stopTime), - (process.env.GH_AW_WORKFLOW_NAME = "test-workflow"), - await eval(`(async () => { ${checkStopTimeScript}; await main(); })()`), - expect(mockCore.setOutput).toHaveBeenCalledWith("stop_time_ok", "true"), - expect(mockCore.setFailed).not.toHaveBeenCalled()); - }); - }), - describe("when stop time has been reached", () => { - it("should set stop_time_ok to false without attempting to disable workflow", async () => { - const pastDate = new Date(); - pastDate.setFullYear(pastDate.getFullYear() - 1); - const stopTime = pastDate.toISOString().replace("T", " ").substring(0, 19); - ((process.env.GH_AW_STOP_TIME = stopTime), - (process.env.GH_AW_WORKFLOW_NAME = "test-workflow"), - await eval(`(async () => { ${checkStopTimeScript}; await main(); })()`), - expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("Stop time reached")), - expect(mockGithub.rest.actions.listRepoWorkflows).not.toHaveBeenCalled(), - expect(mockGithub.rest.actions.disableWorkflow).not.toHaveBeenCalled(), - expect(mockCore.setOutput).toHaveBeenCalledWith("stop_time_ok", "false"), - expect(mockCore.setFailed).not.toHaveBeenCalled()); - }); - })); - })); + debug: vi.fn(), + info: vi.fn(), + notice: vi.fn(), + warning: vi.fn(), + error: vi.fn(), + setFailed: vi.fn(), + setOutput: vi.fn(), + exportVariable: vi.fn(), + setSecret: vi.fn(), + setCancelled: vi.fn(), + setError: vi.fn(), + getInput: vi.fn(), + getBooleanInput: vi.fn(), + getMultilineInput: vi.fn(), + getState: vi.fn(), + saveState: vi.fn(), + startGroup: vi.fn(), + endGroup: vi.fn(), + group: vi.fn(), + addPath: vi.fn(), + setCommandEcho: vi.fn(), + isDebug: vi.fn().mockReturnValue(false), + getIDToken: vi.fn(), + toPlatformPath: vi.fn(), + toPosixPath: vi.fn(), + toWin32Path: vi.fn(), + summary: { addRaw: vi.fn().mockReturnThis(), write: vi.fn().mockResolvedValue() }, +}; + +const mockGithub = { + rest: { actions: { listRepoWorkflows: vi.fn(), disableWorkflow: vi.fn() } }, +}; + +const mockContext = { repo: { owner: "testowner", repo: "testrepo" } }; + +global.core = mockCore; +global.github = mockGithub; +global.context = mockContext; + +describe("check_stop_time.cjs", () => { + let checkStopTimeScript; + let originalEnv; + + beforeEach(() => { + vi.clearAllMocks(); + originalEnv = { + GH_AW_STOP_TIME: process.env.GH_AW_STOP_TIME, + GH_AW_WORKFLOW_NAME: process.env.GH_AW_WORKFLOW_NAME, + }; + const scriptPath = path.join(process.cwd(), "check_stop_time.cjs"); + checkStopTimeScript = fs.readFileSync(scriptPath, "utf8"); + }); + + afterEach(() => { + if (originalEnv.GH_AW_STOP_TIME !== undefined) { + process.env.GH_AW_STOP_TIME = originalEnv.GH_AW_STOP_TIME; + } else { + delete process.env.GH_AW_STOP_TIME; + } + + if (originalEnv.GH_AW_WORKFLOW_NAME !== undefined) { + process.env.GH_AW_WORKFLOW_NAME = originalEnv.GH_AW_WORKFLOW_NAME; + } else { + delete process.env.GH_AW_WORKFLOW_NAME; + } + }); + + describe("when stop time is not configured", () => { + it("should fail if GH_AW_STOP_TIME is not set", async () => { + delete process.env.GH_AW_STOP_TIME; + process.env.GH_AW_WORKFLOW_NAME = "test-workflow"; + + await eval(`(async () => { ${checkStopTimeScript}; await main(); })()`); + + expect(mockCore.setFailed).toHaveBeenCalledWith(expect.stringContaining("GH_AW_STOP_TIME not specified")); + expect(mockCore.setOutput).not.toHaveBeenCalled(); + }); + + it("should fail if GH_AW_WORKFLOW_NAME is not set", async () => { + process.env.GH_AW_STOP_TIME = "2025-12-31 23:59:59"; + delete process.env.GH_AW_WORKFLOW_NAME; + + await eval(`(async () => { ${checkStopTimeScript}; await main(); })()`); + + expect(mockCore.setFailed).toHaveBeenCalledWith(expect.stringContaining("GH_AW_WORKFLOW_NAME not specified")); + expect(mockCore.setOutput).not.toHaveBeenCalled(); + }); + }); + + describe("when stop time format is invalid", () => { + it("should fail with error for invalid format", async () => { + process.env.GH_AW_STOP_TIME = "invalid-date"; + process.env.GH_AW_WORKFLOW_NAME = "test-workflow"; + + await eval(`(async () => { ${checkStopTimeScript}; await main(); })()`); + + expect(mockCore.setFailed).toHaveBeenCalledWith(expect.stringContaining("Invalid stop-time format")); + expect(mockCore.setOutput).not.toHaveBeenCalled(); + }); + }); + + describe("when stop time is in the future", () => { + it("should allow execution", async () => { + const futureDate = new Date(); + futureDate.setFullYear(futureDate.getFullYear() + 1); + const stopTime = futureDate.toISOString().replace("T", " ").substring(0, 19); + + process.env.GH_AW_STOP_TIME = stopTime; + process.env.GH_AW_WORKFLOW_NAME = "test-workflow"; + + await eval(`(async () => { ${checkStopTimeScript}; await main(); })()`); + + expect(mockCore.setOutput).toHaveBeenCalledWith("stop_time_ok", "true"); + expect(mockCore.setFailed).not.toHaveBeenCalled(); + }); + }); + + describe("when stop time has been reached", () => { + it("should set stop_time_ok to false without attempting to disable workflow", async () => { + const pastDate = new Date(); + pastDate.setFullYear(pastDate.getFullYear() - 1); + const stopTime = pastDate.toISOString().replace("T", " ").substring(0, 19); + + process.env.GH_AW_STOP_TIME = stopTime; + process.env.GH_AW_WORKFLOW_NAME = "test-workflow"; + + await eval(`(async () => { ${checkStopTimeScript}; await main(); })()`); + + expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("Stop time reached")); + expect(mockGithub.rest.actions.listRepoWorkflows).not.toHaveBeenCalled(); + expect(mockGithub.rest.actions.disableWorkflow).not.toHaveBeenCalled(); + expect(mockCore.setOutput).toHaveBeenCalledWith("stop_time_ok", "false"); + expect(mockCore.setFailed).not.toHaveBeenCalled(); + }); + }); +});