diff --git a/__tests__/restore.test.ts b/__tests__/restore.test.ts index e854f6c..b4dab7e 100644 --- a/__tests__/restore.test.ts +++ b/__tests__/restore.test.ts @@ -5,7 +5,7 @@ import * as io from "@actions/io"; import * as path from "path"; import * as cacheHttpClient from "../src/cacheHttpClient"; -import { Inputs } from "../src/constants"; +import { Events, Inputs } from "../src/constants"; import { ArtifactCacheEntry } from "../src/contracts"; import run from "../src/restore"; import * as actionUtils from "../src/utils/actionUtils"; @@ -28,12 +28,33 @@ beforeAll(() => { } ); + jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => { + const actualUtils = jest.requireActual("../src/utils/actionUtils"); + return actualUtils.isValidEvent(); + }); + jest.spyOn(io, "which").mockImplementation(tool => { return Promise.resolve(tool); }); }); + +beforeEach(() => { + process.env[Events.Key] = Events.Push; +}); + afterEach(() => { testUtils.clearInputs(); + delete process.env[Events.Key]; +}); + +test("restore with invalid event", async () => { + const failedMock = jest.spyOn(core, "setFailed"); + const invalidEvent = "commit_comment"; + process.env[Events.Key] = invalidEvent; + await run(); + expect(failedMock).toHaveBeenCalledWith( + `Event Validation Error: The event type ${invalidEvent} is not supported. Only \`push\` and \`pull_request\` events are supported at this time.` + ); }); test("restore with no path should fail", async () => { @@ -257,6 +278,82 @@ test("restore with cache found", async () => { expect(failedMock).toHaveBeenCalledTimes(0); }); +test("restore with a pull request event and cache found", async () => { + const key = "node-test"; + const cachePath = path.resolve("node_modules"); + testUtils.setInputs({ + path: "node_modules", + key + }); + + process.env[Events.Key] = Events.PullRequest; + + const infoMock = jest.spyOn(core, "info"); + const warningMock = jest.spyOn(core, "warning"); + const failedMock = jest.spyOn(core, "setFailed"); + const stateMock = jest.spyOn(core, "saveState"); + + const cacheEntry: ArtifactCacheEntry = { + cacheKey: key, + scope: "refs/heads/master", + archiveLocation: "https://www.example.com/download" + }; + const getCacheMock = jest.spyOn(cacheHttpClient, "getCacheEntry"); + getCacheMock.mockImplementation(_ => { + return Promise.resolve(cacheEntry); + }); + const tempPath = "/foo/bar"; + + const createTempDirectoryMock = jest.spyOn( + actionUtils, + "createTempDirectory" + ); + createTempDirectoryMock.mockImplementation(() => { + return Promise.resolve(tempPath); + }); + + const archivePath = path.join(tempPath, "cache.tgz"); + const setCacheStateMock = jest.spyOn(actionUtils, "setCacheState"); + const downloadCacheMock = jest.spyOn(cacheHttpClient, "downloadCache"); + + const fileSize = 142; + const getArchiveFileSizeMock = jest + .spyOn(actionUtils, "getArchiveFileSize") + .mockReturnValue(fileSize); + + const mkdirMock = jest.spyOn(io, "mkdirP"); + const execMock = jest.spyOn(exec, "exec"); + const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput"); + + await run(); + + expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); + expect(getCacheMock).toHaveBeenCalledWith([key]); + expect(setCacheStateMock).toHaveBeenCalledWith(cacheEntry); + expect(createTempDirectoryMock).toHaveBeenCalledTimes(1); + expect(downloadCacheMock).toHaveBeenCalledWith(cacheEntry, archivePath); + expect(getArchiveFileSizeMock).toHaveBeenCalledWith(archivePath); + expect(mkdirMock).toHaveBeenCalledWith(cachePath); + + const IS_WINDOWS = process.platform === "win32"; + const tarArchivePath = IS_WINDOWS + ? archivePath.replace(/\\/g, "/") + : archivePath; + const tarCachePath = IS_WINDOWS ? cachePath.replace(/\\/g, "/") : cachePath; + const args = IS_WINDOWS ? ["-xz", "--force-local"] : ["-xz"]; + args.push(...["-f", tarArchivePath, "-C", tarCachePath]); + + expect(execMock).toHaveBeenCalledTimes(1); + expect(execMock).toHaveBeenCalledWith(`"tar"`, args); + + expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); + expect(setCacheHitOutputMock).toHaveBeenCalledWith(true); + + expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`); + expect(warningMock).toHaveBeenCalledTimes(0); + expect(failedMock).toHaveBeenCalledTimes(0); +}); + test("restore with cache found for restore key", async () => { const key = "node-test"; const restoreKey = "node-"; diff --git a/src/constants.ts b/src/constants.ts index 80f6de9..86048b4 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -12,3 +12,9 @@ export namespace State { export const CacheKey = "CACHE_KEY"; export const CacheResult = "CACHE_RESULT"; } + +export namespace Events { + export const Key = "GITHUB_EVENT_NAME"; + export const Push = "push"; + export const PullRequest = "pull_request"; +} diff --git a/src/restore.ts b/src/restore.ts index 4080098..977376e 100644 --- a/src/restore.ts +++ b/src/restore.ts @@ -11,6 +11,12 @@ import * as utils from "./utils/actionUtils"; async function run() { try { // Validate inputs, this can cause task failure + if (!utils.isValidEvent()) { + core.setFailed( + `Event Validation Error: The event type ${process.env["GITHUB_EVENT_NAME"]} is not supported. Only \`push\` and \`pull_request\` events are supported at this time.` + ); + } + let cachePath = utils.resolvePath( core.getInput(Inputs.Path, { required: true }) ); diff --git a/src/utils/actionUtils.ts b/src/utils/actionUtils.ts index 9ad0072..31fcdc9 100644 --- a/src/utils/actionUtils.ts +++ b/src/utils/actionUtils.ts @@ -5,7 +5,7 @@ import * as os from "os"; import * as path from "path"; import * as uuidV4 from "uuid/v4"; -import { Outputs, State } from "../constants"; +import { Events, Outputs, State } from "../constants"; import { ArtifactCacheEntry } from "../contracts"; // From https://github.com/actions/toolkit/blob/master/packages/tool-cache/src/tool-cache.ts#L23 @@ -84,3 +84,11 @@ export function resolvePath(filePath: string): string { return path.resolve(filePath); } + +// Currently the cache token is only authorized for push and pull_request events +// All other events will fail when reading and saving the cache +// See GitHub Context https://help.github.com/actions/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions#github-context +export function isValidEvent(): boolean { + const githubEvent = process.env[Events.Key] || ""; + return githubEvent === Events.Push || githubEvent === Events.PullRequest; +}