Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions actions/setup/js/check_stop_time.cjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
// @ts-check
/// <reference types="@actions/github-script" />

/**
* 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<void>}
*/
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.");
Expand All @@ -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;
}
Expand Down
234 changes: 137 additions & 97 deletions actions/setup/js/check_stop_time.test.cjs
Original file line number Diff line number Diff line change
@@ -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();
});
});
});