From 3d4af52c521715f5340e72ebcd7e6d87fc0915ce Mon Sep 17 00:00:00 2001 From: Sankalp Kotewar <98868223+kotewar@users.noreply.github.com> Date: Sun, 11 Dec 2022 13:33:36 +0000 Subject: [PATCH] Renamed variables and added tests --- __tests__/restore.test.ts | 154 +------------- __tests__/restoreImpl.test.ts | 326 ++++++++++++++++++++++++++++ __tests__/restoreOnly.test.ts | 190 ++--------------- __tests__/save.test.ts | 288 ------------------------- __tests__/saveImpl.test.ts | 388 ++++++++++++++++++++++++++++++++++ __tests__/saveOnly.test.ts | 302 +------------------------- dist/restore-only/index.js | 25 +-- dist/restore/index.js | 25 +-- dist/save-only/index.js | 23 +- dist/save/index.js | 23 +- restore/action.yml | 6 +- save/action.yml | 3 - src/constants.ts | 18 +- src/restoreImpl.ts | 12 +- src/saveImpl.ts | 3 +- src/stateProvider.ts | 12 +- 16 files changed, 810 insertions(+), 988 deletions(-) create mode 100644 __tests__/restoreImpl.test.ts create mode 100644 __tests__/saveImpl.test.ts diff --git a/__tests__/restore.test.ts b/__tests__/restore.test.ts index 6d78052..4202946 100644 --- a/__tests__/restore.test.ts +++ b/__tests__/restore.test.ts @@ -1,7 +1,7 @@ import * as cache from "@actions/cache"; import * as core from "@actions/core"; -import { Events, Inputs, RefKey } from "../src/constants"; +import { Events, RefKey } from "../src/constants"; import run from "../src/restore"; import * as actionUtils from "../src/utils/actionUtils"; import * as testUtils from "../src/utils/testUtils"; @@ -45,158 +45,6 @@ afterEach(() => { delete process.env[RefKey]; }); -test("restore with invalid event outputs warning", async () => { - const logWarningMock = jest.spyOn(actionUtils, "logWarning"); - const failedMock = jest.spyOn(core, "setFailed"); - const invalidEvent = "commit_comment"; - process.env[Events.Key] = invalidEvent; - delete process.env[RefKey]; - await run(); - expect(logWarningMock).toHaveBeenCalledWith( - `Event Validation Error: The event type ${invalidEvent} is not supported because it's not tied to a branch or tag ref.` - ); - expect(failedMock).toHaveBeenCalledTimes(0); -}); - -test("restore without AC available should no-op", async () => { - jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false); - jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation( - () => false - ); - - const restoreCacheMock = jest.spyOn(cache, "restoreCache"); - const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput"); - - await run(); - - expect(restoreCacheMock).toHaveBeenCalledTimes(0); - expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); - expect(setCacheHitOutputMock).toHaveBeenCalledWith(false); -}); - -test("restore on GHES without AC available should no-op", async () => { - jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true); - jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation( - () => false - ); - - const restoreCacheMock = jest.spyOn(cache, "restoreCache"); - const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput"); - - await run(); - - expect(restoreCacheMock).toHaveBeenCalledTimes(0); - expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); - expect(setCacheHitOutputMock).toHaveBeenCalledWith(false); -}); - -test("restore on GHES with AC available ", async () => { - jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true); - const path = "node_modules"; - const key = "node-test"; - testUtils.setInputs({ - path: path, - key - }); - - const infoMock = jest.spyOn(core, "info"); - const failedMock = jest.spyOn(core, "setFailed"); - const stateMock = jest.spyOn(core, "saveState"); - const setCacheHitOutputMock = jest.spyOn(core, "setOutput"); - const restoreCacheMock = jest - .spyOn(cache, "restoreCache") - .mockImplementationOnce(() => { - return Promise.resolve(key); - }); - - await run(); - - expect(restoreCacheMock).toHaveBeenCalledTimes(1); - expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []); - - expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); - expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); - expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "true"); - - expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`); - expect(failedMock).toHaveBeenCalledTimes(0); -}); - -test("restore with no path should fail", async () => { - const failedMock = jest.spyOn(core, "setFailed"); - const restoreCacheMock = jest.spyOn(cache, "restoreCache"); - await run(); - expect(restoreCacheMock).toHaveBeenCalledTimes(0); - // this input isn't necessary for restore b/c tarball contains entries relative to workspace - expect(failedMock).not.toHaveBeenCalledWith( - "Input required and not supplied: path" - ); -}); - -test("restore with no key", async () => { - testUtils.setInput(Inputs.Path, "node_modules"); - const failedMock = jest.spyOn(core, "setFailed"); - const restoreCacheMock = jest.spyOn(cache, "restoreCache"); - await run(); - expect(restoreCacheMock).toHaveBeenCalledTimes(0); - expect(failedMock).toHaveBeenCalledWith( - "Input required and not supplied: key" - ); -}); - -test("restore with too many keys should fail", async () => { - const path = "node_modules"; - const key = "node-test"; - const restoreKeys = [...Array(20).keys()].map(x => x.toString()); - testUtils.setInputs({ - path: path, - key, - restoreKeys - }); - const failedMock = jest.spyOn(core, "setFailed"); - const restoreCacheMock = jest.spyOn(cache, "restoreCache"); - await run(); - expect(restoreCacheMock).toHaveBeenCalledTimes(1); - expect(restoreCacheMock).toHaveBeenCalledWith([path], key, restoreKeys); - expect(failedMock).toHaveBeenCalledWith( - `Key Validation Error: Keys are limited to a maximum of 10.` - ); -}); - -test("restore with large key should fail", async () => { - const path = "node_modules"; - const key = "foo".repeat(512); // Over the 512 character limit - testUtils.setInputs({ - path: path, - key - }); - const failedMock = jest.spyOn(core, "setFailed"); - const restoreCacheMock = jest.spyOn(cache, "restoreCache"); - await run(); - expect(restoreCacheMock).toHaveBeenCalledTimes(1); - expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []); - expect(failedMock).toHaveBeenCalledWith( - `Key Validation Error: ${key} cannot be larger than 512 characters.` - ); -}); - -test("restore with invalid key should fail", async () => { - const path = "node_modules"; - const key = "comma,comma"; - testUtils.setInputs({ - path: path, - key - }); - const failedMock = jest.spyOn(core, "setFailed"); - const restoreCacheMock = jest.spyOn(cache, "restoreCache"); - await run(); - expect(restoreCacheMock).toHaveBeenCalledTimes(1); - expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []); - expect(failedMock).toHaveBeenCalledWith( - `Key Validation Error: ${key} cannot contain commas.` - ); -}); - test("restore with no cache found", async () => { const path = "node_modules"; const key = "node-test"; diff --git a/__tests__/restoreImpl.test.ts b/__tests__/restoreImpl.test.ts new file mode 100644 index 0000000..66bab8d --- /dev/null +++ b/__tests__/restoreImpl.test.ts @@ -0,0 +1,326 @@ +import * as cache from "@actions/cache"; +import * as core from "@actions/core"; + +import { Events, Inputs, RefKey } from "../src/constants"; +import run from "../src/restoreImpl"; +import { StateProvider } from "../src/stateProvider"; +import * as actionUtils from "../src/utils/actionUtils"; +import * as testUtils from "../src/utils/testUtils"; + +jest.mock("../src/utils/actionUtils"); + +beforeAll(() => { + jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation( + (key, cacheResult) => { + const actualUtils = jest.requireActual("../src/utils/actionUtils"); + return actualUtils.isExactKeyMatch(key, cacheResult); + } + ); + + jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => { + const actualUtils = jest.requireActual("../src/utils/actionUtils"); + return actualUtils.isValidEvent(); + }); + + jest.spyOn(actionUtils, "getInputAsArray").mockImplementation( + (name, options) => { + const actualUtils = jest.requireActual("../src/utils/actionUtils"); + return actualUtils.getInputAsArray(name, options); + } + ); +}); + +beforeEach(() => { + process.env[Events.Key] = Events.Push; + process.env[RefKey] = "refs/heads/feature-branch"; + + jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false); + jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation( + () => true + ); +}); + +afterEach(() => { + testUtils.clearInputs(); + delete process.env[Events.Key]; + delete process.env[RefKey]; +}); + +test("restore with invalid event outputs warning", async () => { + const logWarningMock = jest.spyOn(actionUtils, "logWarning"); + const failedMock = jest.spyOn(core, "setFailed"); + const invalidEvent = "commit_comment"; + process.env[Events.Key] = invalidEvent; + delete process.env[RefKey]; + await run(new StateProvider()); + expect(logWarningMock).toHaveBeenCalledWith( + `Event Validation Error: The event type ${invalidEvent} is not supported because it's not tied to a branch or tag ref.` + ); + expect(failedMock).toHaveBeenCalledTimes(0); +}); + +test("restore without AC available should no-op", async () => { + jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false); + jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation( + () => false + ); + + const restoreCacheMock = jest.spyOn(cache, "restoreCache"); + const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput"); + + await run(new StateProvider()); + + expect(restoreCacheMock).toHaveBeenCalledTimes(0); + expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); + expect(setCacheHitOutputMock).toHaveBeenCalledWith(false); +}); + +test("restore on GHES without AC available should no-op", async () => { + jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true); + jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation( + () => false + ); + + const restoreCacheMock = jest.spyOn(cache, "restoreCache"); + const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput"); + + await run(new StateProvider()); + + expect(restoreCacheMock).toHaveBeenCalledTimes(0); + expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); + expect(setCacheHitOutputMock).toHaveBeenCalledWith(false); +}); + +test("restore on GHES with AC available ", async () => { + jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true); + const path = "node_modules"; + const key = "node-test"; + testUtils.setInputs({ + path: path, + key + }); + + const infoMock = jest.spyOn(core, "info"); + const failedMock = jest.spyOn(core, "setFailed"); + const stateMock = jest.spyOn(core, "saveState"); + const setCacheHitOutputMock = jest.spyOn(core, "setOutput"); + const restoreCacheMock = jest + .spyOn(cache, "restoreCache") + .mockImplementationOnce(() => { + return Promise.resolve(key); + }); + + await run(new StateProvider()); + + expect(restoreCacheMock).toHaveBeenCalledTimes(1); + expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []); + + expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); + expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); + expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "true"); + + expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`); + expect(failedMock).toHaveBeenCalledTimes(0); +}); + +test("restore with no path should fail", async () => { + const failedMock = jest.spyOn(core, "setFailed"); + const restoreCacheMock = jest.spyOn(cache, "restoreCache"); + await run(new StateProvider()); + expect(restoreCacheMock).toHaveBeenCalledTimes(0); + // this input isn't necessary for restore b/c tarball contains entries relative to workspace + expect(failedMock).not.toHaveBeenCalledWith( + "Input required and not supplied: path" + ); +}); + +test("restore with no key", async () => { + testUtils.setInput(Inputs.Path, "node_modules"); + const failedMock = jest.spyOn(core, "setFailed"); + const restoreCacheMock = jest.spyOn(cache, "restoreCache"); + await run(new StateProvider()); + expect(restoreCacheMock).toHaveBeenCalledTimes(0); + expect(failedMock).toHaveBeenCalledWith( + "Input required and not supplied: key" + ); +}); + +test("restore with too many keys should fail", async () => { + const path = "node_modules"; + const key = "node-test"; + const restoreKeys = [...Array(20).keys()].map(x => x.toString()); + testUtils.setInputs({ + path: path, + key, + restoreKeys + }); + const failedMock = jest.spyOn(core, "setFailed"); + const restoreCacheMock = jest.spyOn(cache, "restoreCache"); + await run(new StateProvider()); + expect(restoreCacheMock).toHaveBeenCalledTimes(1); + expect(restoreCacheMock).toHaveBeenCalledWith([path], key, restoreKeys); + expect(failedMock).toHaveBeenCalledWith( + `Key Validation Error: Keys are limited to a maximum of 10.` + ); +}); + +test("restore with large key should fail", async () => { + const path = "node_modules"; + const key = "foo".repeat(512); // Over the 512 character limit + testUtils.setInputs({ + path: path, + key + }); + const failedMock = jest.spyOn(core, "setFailed"); + const restoreCacheMock = jest.spyOn(cache, "restoreCache"); + await run(new StateProvider()); + expect(restoreCacheMock).toHaveBeenCalledTimes(1); + expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []); + expect(failedMock).toHaveBeenCalledWith( + `Key Validation Error: ${key} cannot be larger than 512 characters.` + ); +}); + +test("restore with invalid key should fail", async () => { + const path = "node_modules"; + const key = "comma,comma"; + testUtils.setInputs({ + path: path, + key + }); + const failedMock = jest.spyOn(core, "setFailed"); + const restoreCacheMock = jest.spyOn(cache, "restoreCache"); + await run(new StateProvider()); + expect(restoreCacheMock).toHaveBeenCalledTimes(1); + expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []); + expect(failedMock).toHaveBeenCalledWith( + `Key Validation Error: ${key} cannot contain commas.` + ); +}); + +test("restore with no cache found", async () => { + const path = "node_modules"; + const key = "node-test"; + testUtils.setInputs({ + path: path, + key + }); + + const infoMock = jest.spyOn(core, "info"); + const failedMock = jest.spyOn(core, "setFailed"); + const stateMock = jest.spyOn(core, "saveState"); + const restoreCacheMock = jest + .spyOn(cache, "restoreCache") + .mockImplementationOnce(() => { + return Promise.resolve(undefined); + }); + + await run(new StateProvider()); + + expect(restoreCacheMock).toHaveBeenCalledTimes(1); + expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []); + + expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); + expect(failedMock).toHaveBeenCalledTimes(0); + + expect(infoMock).toHaveBeenCalledWith( + `Cache not found for input keys: ${key}` + ); +}); + +test("restore with restore keys and no cache found", async () => { + const path = "node_modules"; + const key = "node-test"; + const restoreKey = "node-"; + testUtils.setInputs({ + path: path, + key, + restoreKeys: [restoreKey] + }); + + const infoMock = jest.spyOn(core, "info"); + const failedMock = jest.spyOn(core, "setFailed"); + const stateMock = jest.spyOn(core, "saveState"); + const restoreCacheMock = jest + .spyOn(cache, "restoreCache") + .mockImplementationOnce(() => { + return Promise.resolve(undefined); + }); + + await run(new StateProvider()); + + expect(restoreCacheMock).toHaveBeenCalledTimes(1); + expect(restoreCacheMock).toHaveBeenCalledWith([path], key, [restoreKey]); + + expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); + expect(failedMock).toHaveBeenCalledTimes(0); + + expect(infoMock).toHaveBeenCalledWith( + `Cache not found for input keys: ${key}, ${restoreKey}` + ); +}); + +test("restore with cache found for key", async () => { + const path = "node_modules"; + const key = "node-test"; + testUtils.setInputs({ + path: path, + key + }); + + const infoMock = jest.spyOn(core, "info"); + const failedMock = jest.spyOn(core, "setFailed"); + const stateMock = jest.spyOn(core, "saveState"); + const setCacheHitOutputMock = jest.spyOn(core, "setOutput"); + const restoreCacheMock = jest + .spyOn(cache, "restoreCache") + .mockImplementationOnce(() => { + return Promise.resolve(key); + }); + + await run(new StateProvider()); + + expect(restoreCacheMock).toHaveBeenCalledTimes(1); + expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []); + + expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); + expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); + expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "true"); + + expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`); + expect(failedMock).toHaveBeenCalledTimes(0); +}); + +test("restore with cache found for restore key", async () => { + const path = "node_modules"; + const key = "node-test"; + const restoreKey = "node-"; + testUtils.setInputs({ + path: path, + key, + restoreKeys: [restoreKey] + }); + + const infoMock = jest.spyOn(core, "info"); + const failedMock = jest.spyOn(core, "setFailed"); + const stateMock = jest.spyOn(core, "saveState"); + const setCacheHitOutputMock = jest.spyOn(core, "setOutput"); + const restoreCacheMock = jest + .spyOn(cache, "restoreCache") + .mockImplementationOnce(() => { + return Promise.resolve(restoreKey); + }); + + await run(new StateProvider()); + + expect(restoreCacheMock).toHaveBeenCalledTimes(1); + expect(restoreCacheMock).toHaveBeenCalledWith([path], key, [restoreKey]); + + expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); + expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); + expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false"); + expect(infoMock).toHaveBeenCalledWith( + `Cache restored from key: ${restoreKey}` + ); + expect(failedMock).toHaveBeenCalledTimes(0); +}); diff --git a/__tests__/restoreOnly.test.ts b/__tests__/restoreOnly.test.ts index ee9779c..4812f02 100644 --- a/__tests__/restoreOnly.test.ts +++ b/__tests__/restoreOnly.test.ts @@ -1,8 +1,9 @@ import * as cache from "@actions/cache"; import * as core from "@actions/core"; -import { Events, Inputs, RefKey } from "../src/constants"; -import run from "../src/restoreOnly"; +import { Events, RefKey } from "../src/constants"; +import run from "../src/restoreImpl"; +import { StateProvider } from "../src/stateProvider"; import * as actionUtils from "../src/utils/actionUtils"; import * as testUtils from "../src/utils/testUtils"; @@ -45,157 +46,6 @@ afterEach(() => { delete process.env[RefKey]; }); -test("restore with invalid event outputs warning", async () => { - const logWarningMock = jest.spyOn(actionUtils, "logWarning"); - const failedMock = jest.spyOn(core, "setFailed"); - const invalidEvent = "commit_comment"; - process.env[Events.Key] = invalidEvent; - delete process.env[RefKey]; - await run(); - expect(logWarningMock).toHaveBeenCalledWith( - `Event Validation Error: The event type ${invalidEvent} is not supported because it's not tied to a branch or tag ref.` - ); - expect(failedMock).toHaveBeenCalledTimes(0); -}); - -test("restore without AC available should no-op", async () => { - jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false); - jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation( - () => false - ); - - const restoreCacheMock = jest.spyOn(cache, "restoreCache"); - const outputMock = jest.spyOn(actionUtils, "setCacheHitOutput"); - - await run(); - - expect(restoreCacheMock).toHaveBeenCalledTimes(0); - expect(outputMock).toHaveBeenCalledTimes(1); - expect(outputMock).toHaveBeenCalledWith(false); -}); - -test("restore on GHES without AC available should no-op", async () => { - jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true); - jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation( - () => false - ); - - const restoreCacheMock = jest.spyOn(cache, "restoreCache"); - const outputMock = jest.spyOn(actionUtils, "setCacheHitOutput"); - - await run(); - - expect(restoreCacheMock).toHaveBeenCalledTimes(0); - expect(outputMock).toHaveBeenCalledTimes(1); - expect(outputMock).toHaveBeenCalledWith(false); -}); - -test("restore on GHES with AC available ", async () => { - jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true); - const path = "node_modules"; - const key = "node-test"; - testUtils.setInputs({ - path: path, - key - }); - - const infoMock = jest.spyOn(core, "info"); - const failedMock = jest.spyOn(core, "setFailed"); - const outputMock = jest.spyOn(core, "setOutput"); - const restoreCacheMock = jest - .spyOn(cache, "restoreCache") - .mockImplementationOnce(() => { - return Promise.resolve(key); - }); - - await run(); - - expect(restoreCacheMock).toHaveBeenCalledTimes(1); - expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []); - - expect(outputMock).toHaveBeenCalledWith("CACHE_KEY", key); - expect(outputMock).toHaveBeenCalledTimes(3); - expect(outputMock).toHaveBeenCalledWith("cache-hit", "true"); - - expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`); - expect(failedMock).toHaveBeenCalledTimes(0); -}); - -test("restore with no path should fail", async () => { - const failedMock = jest.spyOn(core, "setFailed"); - const restoreCacheMock = jest.spyOn(cache, "restoreCache"); - await run(); - expect(restoreCacheMock).toHaveBeenCalledTimes(0); - // this input isn't necessary for restore b/c tarball contains entries relative to workspace - expect(failedMock).not.toHaveBeenCalledWith( - "Input required and not supplied: path" - ); -}); - -test("restore with no key", async () => { - testUtils.setInput(Inputs.Path, "node_modules"); - const failedMock = jest.spyOn(core, "setFailed"); - const restoreCacheMock = jest.spyOn(cache, "restoreCache"); - await run(); - expect(restoreCacheMock).toHaveBeenCalledTimes(0); - expect(failedMock).toHaveBeenCalledWith( - "Input required and not supplied: key" - ); -}); - -test("restore with too many keys should fail", async () => { - const path = "node_modules"; - const key = "node-test"; - const restoreKeys = [...Array(20).keys()].map(x => x.toString()); - testUtils.setInputs({ - path: path, - key, - restoreKeys - }); - const failedMock = jest.spyOn(core, "setFailed"); - const restoreCacheMock = jest.spyOn(cache, "restoreCache"); - await run(); - expect(restoreCacheMock).toHaveBeenCalledTimes(1); - expect(restoreCacheMock).toHaveBeenCalledWith([path], key, restoreKeys); - expect(failedMock).toHaveBeenCalledWith( - `Key Validation Error: Keys are limited to a maximum of 10.` - ); -}); - -test("restore with large key should fail", async () => { - const path = "node_modules"; - const key = "foo".repeat(512); // Over the 512 character limit - testUtils.setInputs({ - path: path, - key - }); - const failedMock = jest.spyOn(core, "setFailed"); - const restoreCacheMock = jest.spyOn(cache, "restoreCache"); - await run(); - expect(restoreCacheMock).toHaveBeenCalledTimes(1); - expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []); - expect(failedMock).toHaveBeenCalledWith( - `Key Validation Error: ${key} cannot be larger than 512 characters.` - ); -}); - -test("restore with invalid key should fail", async () => { - const path = "node_modules"; - const key = "comma,comma"; - testUtils.setInputs({ - path: path, - key - }); - const failedMock = jest.spyOn(core, "setFailed"); - const restoreCacheMock = jest.spyOn(cache, "restoreCache"); - await run(); - expect(restoreCacheMock).toHaveBeenCalledTimes(1); - expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []); - expect(failedMock).toHaveBeenCalledWith( - `Key Validation Error: ${key} cannot contain commas.` - ); -}); - test("restore with no cache found", async () => { const path = "node_modules"; const key = "node-test"; @@ -206,19 +56,19 @@ test("restore with no cache found", async () => { const infoMock = jest.spyOn(core, "info"); const failedMock = jest.spyOn(core, "setFailed"); - const outputMock = jest.spyOn(core, "setOutput"); + const stateMock = jest.spyOn(core, "saveState"); const restoreCacheMock = jest .spyOn(cache, "restoreCache") .mockImplementationOnce(() => { return Promise.resolve(undefined); }); - await run(); + await run(new StateProvider()); expect(restoreCacheMock).toHaveBeenCalledTimes(1); expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []); - expect(outputMock).toHaveBeenCalledWith("CACHE_KEY", key); + expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); expect(failedMock).toHaveBeenCalledTimes(0); expect(infoMock).toHaveBeenCalledWith( @@ -238,19 +88,19 @@ test("restore with restore keys and no cache found", async () => { const infoMock = jest.spyOn(core, "info"); const failedMock = jest.spyOn(core, "setFailed"); - const outputMock = jest.spyOn(core, "setOutput"); + const stateMock = jest.spyOn(core, "saveState"); const restoreCacheMock = jest .spyOn(cache, "restoreCache") .mockImplementationOnce(() => { return Promise.resolve(undefined); }); - await run(); + await run(new StateProvider()); expect(restoreCacheMock).toHaveBeenCalledTimes(1); expect(restoreCacheMock).toHaveBeenCalledWith([path], key, [restoreKey]); - expect(outputMock).toHaveBeenCalledWith("CACHE_KEY", key); + expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); expect(failedMock).toHaveBeenCalledTimes(0); expect(infoMock).toHaveBeenCalledWith( @@ -268,21 +118,22 @@ test("restore with cache found for key", async () => { const infoMock = jest.spyOn(core, "info"); const failedMock = jest.spyOn(core, "setFailed"); - const outputMock = jest.spyOn(core, "setOutput"); + const stateMock = jest.spyOn(core, "saveState"); + const setCacheHitOutputMock = jest.spyOn(core, "setOutput"); const restoreCacheMock = jest .spyOn(cache, "restoreCache") .mockImplementationOnce(() => { return Promise.resolve(key); }); - await run(); + await run(new StateProvider()); expect(restoreCacheMock).toHaveBeenCalledTimes(1); expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []); - expect(outputMock).toHaveBeenCalledWith("CACHE_KEY", key); - expect(outputMock).toHaveBeenCalledTimes(3); - expect(outputMock).toHaveBeenCalledWith("cache-hit", "true"); + expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); + expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); + expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "true"); expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`); expect(failedMock).toHaveBeenCalledTimes(0); @@ -300,21 +151,22 @@ test("restore with cache found for restore key", async () => { const infoMock = jest.spyOn(core, "info"); const failedMock = jest.spyOn(core, "setFailed"); - const outputMock = jest.spyOn(core, "setOutput"); + const stateMock = jest.spyOn(core, "saveState"); + const setCacheHitOutputMock = jest.spyOn(core, "setOutput"); const restoreCacheMock = jest .spyOn(cache, "restoreCache") .mockImplementationOnce(() => { return Promise.resolve(restoreKey); }); - await run(); + await run(new StateProvider()); expect(restoreCacheMock).toHaveBeenCalledTimes(1); expect(restoreCacheMock).toHaveBeenCalledWith([path], key, [restoreKey]); - expect(outputMock).toHaveBeenCalledWith("CACHE_KEY", key); - expect(outputMock).toHaveBeenCalledTimes(3); - expect(outputMock).toHaveBeenCalledWith("cache-hit", "false"); + expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); + expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); + expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false"); expect(infoMock).toHaveBeenCalledWith( `Cache restored from key: ${restoreKey}` ); diff --git a/__tests__/save.test.ts b/__tests__/save.test.ts index 3c550e0..8b3b356 100644 --- a/__tests__/save.test.ts +++ b/__tests__/save.test.ts @@ -61,294 +61,6 @@ afterEach(() => { delete process.env[RefKey]; }); -test("save with invalid event outputs warning", async () => { - const logWarningMock = jest.spyOn(actionUtils, "logWarning"); - const failedMock = jest.spyOn(core, "setFailed"); - const invalidEvent = "commit_comment"; - process.env[Events.Key] = invalidEvent; - delete process.env[RefKey]; - await run(); - expect(logWarningMock).toHaveBeenCalledWith( - `Event Validation Error: The event type ${invalidEvent} is not supported because it's not tied to a branch or tag ref.` - ); - expect(failedMock).toHaveBeenCalledTimes(0); -}); - -test("save with no primary key in state outputs warning", async () => { - const logWarningMock = jest.spyOn(actionUtils, "logWarning"); - const failedMock = jest.spyOn(core, "setFailed"); - - const savedCacheKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; - jest.spyOn(core, "getState") - // Cache Entry State - .mockImplementationOnce(() => { - return ""; - }) - // Cache Key State - .mockImplementationOnce(() => { - return savedCacheKey; - }); - const saveCacheMock = jest.spyOn(cache, "saveCache"); - - await run(); - - expect(saveCacheMock).toHaveBeenCalledTimes(0); - expect(logWarningMock).toHaveBeenCalledWith( - `Error retrieving key from state.` - ); - expect(logWarningMock).toHaveBeenCalledTimes(1); - expect(failedMock).toHaveBeenCalledTimes(0); -}); - -test("save without AC available should no-op", async () => { - jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation( - () => false - ); - - const saveCacheMock = jest.spyOn(cache, "saveCache"); - - await run(); - - expect(saveCacheMock).toHaveBeenCalledTimes(0); -}); - -test("save on ghes without AC available should no-op", async () => { - jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true); - jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation( - () => false - ); - - const saveCacheMock = jest.spyOn(cache, "saveCache"); - - await run(); - - expect(saveCacheMock).toHaveBeenCalledTimes(0); -}); - -test("save on GHES with AC available", async () => { - jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true); - const failedMock = jest.spyOn(core, "setFailed"); - - const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; - const savedCacheKey = "Linux-node-"; - - jest.spyOn(core, "getState") - // Cache Entry State - .mockImplementationOnce(() => { - return savedCacheKey; - }) - // Cache Key State - .mockImplementationOnce(() => { - return primaryKey; - }); - - const inputPath = "node_modules"; - testUtils.setInput(Inputs.Path, inputPath); - testUtils.setInput(Inputs.UploadChunkSize, "4000000"); - - const cacheId = 4; - const saveCacheMock = jest - .spyOn(cache, "saveCache") - .mockImplementationOnce(() => { - return Promise.resolve(cacheId); - }); - - await run(); - - expect(saveCacheMock).toHaveBeenCalledTimes(1); - expect(saveCacheMock).toHaveBeenCalledWith([inputPath], primaryKey, { - uploadChunkSize: 4000000 - }); - - expect(failedMock).toHaveBeenCalledTimes(0); -}); - -test("save with exact match returns early", async () => { - const infoMock = jest.spyOn(core, "info"); - const failedMock = jest.spyOn(core, "setFailed"); - - const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; - const savedCacheKey = primaryKey; - - jest.spyOn(core, "getState") - // Cache Entry State - .mockImplementationOnce(() => { - return savedCacheKey; - }) - // Cache Key State - .mockImplementationOnce(() => { - return primaryKey; - }); - const saveCacheMock = jest.spyOn(cache, "saveCache"); - - await run(); - - expect(saveCacheMock).toHaveBeenCalledTimes(0); - expect(infoMock).toHaveBeenCalledWith( - `Cache hit occurred on the primary key ${primaryKey}, not saving cache.` - ); - expect(failedMock).toHaveBeenCalledTimes(0); -}); - -test("save with missing input outputs warning", async () => { - const logWarningMock = jest.spyOn(actionUtils, "logWarning"); - const failedMock = jest.spyOn(core, "setFailed"); - - const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; - const savedCacheKey = "Linux-node-"; - - jest.spyOn(core, "getState") - // Cache Entry State - .mockImplementationOnce(() => { - return savedCacheKey; - }) - // Cache Key State - .mockImplementationOnce(() => { - return primaryKey; - }); - const saveCacheMock = jest.spyOn(cache, "saveCache"); - - await run(); - - expect(saveCacheMock).toHaveBeenCalledTimes(0); - expect(logWarningMock).toHaveBeenCalledWith( - "Input required and not supplied: path" - ); - expect(logWarningMock).toHaveBeenCalledTimes(1); - expect(failedMock).toHaveBeenCalledTimes(0); -}); - -test("save with large cache outputs warning", async () => { - const logWarningMock = jest.spyOn(actionUtils, "logWarning"); - const failedMock = jest.spyOn(core, "setFailed"); - - const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; - const savedCacheKey = "Linux-node-"; - - jest.spyOn(core, "getState") - // Cache Entry State - .mockImplementationOnce(() => { - return savedCacheKey; - }) - // Cache Key State - .mockImplementationOnce(() => { - return primaryKey; - }); - - const inputPath = "node_modules"; - testUtils.setInput(Inputs.Path, inputPath); - - const saveCacheMock = jest - .spyOn(cache, "saveCache") - .mockImplementationOnce(() => { - throw new Error( - "Cache size of ~6144 MB (6442450944 B) is over the 5GB limit, not saving cache." - ); - }); - - await run(); - - expect(saveCacheMock).toHaveBeenCalledTimes(1); - expect(saveCacheMock).toHaveBeenCalledWith( - [inputPath], - primaryKey, - expect.anything() - ); - - expect(logWarningMock).toHaveBeenCalledTimes(1); - expect(logWarningMock).toHaveBeenCalledWith( - "Cache size of ~6144 MB (6442450944 B) is over the 5GB limit, not saving cache." - ); - expect(failedMock).toHaveBeenCalledTimes(0); -}); - -test("save with reserve cache failure outputs warning", async () => { - const logWarningMock = jest.spyOn(actionUtils, "logWarning"); - const failedMock = jest.spyOn(core, "setFailed"); - - const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; - const savedCacheKey = "Linux-node-"; - - jest.spyOn(core, "getState") - // Cache Entry State - .mockImplementationOnce(() => { - return savedCacheKey; - }) - // Cache Key State - .mockImplementationOnce(() => { - return primaryKey; - }); - - const inputPath = "node_modules"; - testUtils.setInput(Inputs.Path, inputPath); - - const saveCacheMock = jest - .spyOn(cache, "saveCache") - .mockImplementationOnce(() => { - const actualCache = jest.requireActual("@actions/cache"); - const error = new actualCache.ReserveCacheError( - `Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.` - ); - throw error; - }); - - await run(); - - expect(saveCacheMock).toHaveBeenCalledTimes(1); - expect(saveCacheMock).toHaveBeenCalledWith( - [inputPath], - primaryKey, - expect.anything() - ); - - expect(logWarningMock).toHaveBeenCalledWith( - `Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.` - ); - expect(logWarningMock).toHaveBeenCalledTimes(1); - expect(failedMock).toHaveBeenCalledTimes(0); -}); - -test("save with server error outputs warning", async () => { - const logWarningMock = jest.spyOn(actionUtils, "logWarning"); - const failedMock = jest.spyOn(core, "setFailed"); - - const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; - const savedCacheKey = "Linux-node-"; - - jest.spyOn(core, "getState") - // Cache Entry State - .mockImplementationOnce(() => { - return savedCacheKey; - }) - // Cache Key State - .mockImplementationOnce(() => { - return primaryKey; - }); - - const inputPath = "node_modules"; - testUtils.setInput(Inputs.Path, inputPath); - - const saveCacheMock = jest - .spyOn(cache, "saveCache") - .mockImplementationOnce(() => { - throw new Error("HTTP Error Occurred"); - }); - - await run(); - - expect(saveCacheMock).toHaveBeenCalledTimes(1); - expect(saveCacheMock).toHaveBeenCalledWith( - [inputPath], - primaryKey, - expect.anything() - ); - - expect(logWarningMock).toHaveBeenCalledTimes(1); - expect(logWarningMock).toHaveBeenCalledWith("HTTP Error Occurred"); - - expect(failedMock).toHaveBeenCalledTimes(0); -}); - test("save with valid inputs uploads a cache", async () => { const failedMock = jest.spyOn(core, "setFailed"); diff --git a/__tests__/saveImpl.test.ts b/__tests__/saveImpl.test.ts new file mode 100644 index 0000000..78e964a --- /dev/null +++ b/__tests__/saveImpl.test.ts @@ -0,0 +1,388 @@ +import * as cache from "@actions/cache"; +import * as core from "@actions/core"; + +import { Events, Inputs, RefKey } from "../src/constants"; +import run from "../src/saveImpl"; +import { StateProvider } from "../src/stateProvider"; +import * as actionUtils from "../src/utils/actionUtils"; +import * as testUtils from "../src/utils/testUtils"; + +jest.mock("@actions/core"); +jest.mock("@actions/cache"); +jest.mock("../src/utils/actionUtils"); + +beforeAll(() => { + jest.spyOn(core, "getInput").mockImplementation((name, options) => { + return jest.requireActual("@actions/core").getInput(name, options); + }); + + jest.spyOn(actionUtils, "getInputAsArray").mockImplementation( + (name, options) => { + return jest + .requireActual("../src/utils/actionUtils") + .getInputAsArray(name, options); + } + ); + + jest.spyOn(actionUtils, "getInputAsInt").mockImplementation( + (name, options) => { + return jest + .requireActual("../src/utils/actionUtils") + .getInputAsInt(name, options); + } + ); + + jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation( + (key, cacheResult) => { + return jest + .requireActual("../src/utils/actionUtils") + .isExactKeyMatch(key, cacheResult); + } + ); + + jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => { + const actualUtils = jest.requireActual("../src/utils/actionUtils"); + return actualUtils.isValidEvent(); + }); +}); + +beforeEach(() => { + process.env[Events.Key] = Events.Push; + process.env[RefKey] = "refs/heads/feature-branch"; + + jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false); + jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation( + () => true + ); +}); + +afterEach(() => { + testUtils.clearInputs(); + delete process.env[Events.Key]; + delete process.env[RefKey]; +}); + +test("save with invalid event outputs warning", async () => { + const logWarningMock = jest.spyOn(actionUtils, "logWarning"); + const failedMock = jest.spyOn(core, "setFailed"); + const invalidEvent = "commit_comment"; + process.env[Events.Key] = invalidEvent; + delete process.env[RefKey]; + await run(new StateProvider()); + expect(logWarningMock).toHaveBeenCalledWith( + `Event Validation Error: The event type ${invalidEvent} is not supported because it's not tied to a branch or tag ref.` + ); + expect(failedMock).toHaveBeenCalledTimes(0); +}); + +test("save with no primary key in state outputs warning", async () => { + const logWarningMock = jest.spyOn(actionUtils, "logWarning"); + const failedMock = jest.spyOn(core, "setFailed"); + + const savedCacheKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; + jest.spyOn(core, "getState") + // Cache Entry State + .mockImplementationOnce(() => { + return ""; + }) + // Cache Key State + .mockImplementationOnce(() => { + return savedCacheKey; + }); + const saveCacheMock = jest.spyOn(cache, "saveCache"); + + await run(new StateProvider()); + + expect(saveCacheMock).toHaveBeenCalledTimes(0); + expect(logWarningMock).toHaveBeenCalledWith( + `Error retrieving key from state.` + ); + expect(logWarningMock).toHaveBeenCalledTimes(1); + expect(failedMock).toHaveBeenCalledTimes(0); +}); + +test("save without AC available should no-op", async () => { + jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation( + () => false + ); + + const saveCacheMock = jest.spyOn(cache, "saveCache"); + + await run(new StateProvider()); + + expect(saveCacheMock).toHaveBeenCalledTimes(0); +}); + +test("save on ghes without AC available should no-op", async () => { + jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true); + jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation( + () => false + ); + + const saveCacheMock = jest.spyOn(cache, "saveCache"); + + await run(new StateProvider()); + + expect(saveCacheMock).toHaveBeenCalledTimes(0); +}); + +test("save on GHES with AC available", async () => { + jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true); + const failedMock = jest.spyOn(core, "setFailed"); + + const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; + const savedCacheKey = "Linux-node-"; + + jest.spyOn(core, "getState") + // Cache Entry State + .mockImplementationOnce(() => { + return savedCacheKey; + }) + // Cache Key State + .mockImplementationOnce(() => { + return primaryKey; + }); + + const inputPath = "node_modules"; + testUtils.setInput(Inputs.Path, inputPath); + testUtils.setInput(Inputs.UploadChunkSize, "4000000"); + + const cacheId = 4; + const saveCacheMock = jest + .spyOn(cache, "saveCache") + .mockImplementationOnce(() => { + return Promise.resolve(cacheId); + }); + + await run(new StateProvider()); + + expect(saveCacheMock).toHaveBeenCalledTimes(1); + expect(saveCacheMock).toHaveBeenCalledWith([inputPath], primaryKey, { + uploadChunkSize: 4000000 + }); + + expect(failedMock).toHaveBeenCalledTimes(0); +}); + +test("save with exact match returns early", async () => { + const infoMock = jest.spyOn(core, "info"); + const failedMock = jest.spyOn(core, "setFailed"); + + const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; + const savedCacheKey = primaryKey; + + jest.spyOn(core, "getState") + // Cache Entry State + .mockImplementationOnce(() => { + return savedCacheKey; + }) + // Cache Key State + .mockImplementationOnce(() => { + return primaryKey; + }); + const saveCacheMock = jest.spyOn(cache, "saveCache"); + + await run(new StateProvider()); + + expect(saveCacheMock).toHaveBeenCalledTimes(0); + expect(infoMock).toHaveBeenCalledWith( + `Cache hit occurred on the primary key ${primaryKey}, not saving cache.` + ); + expect(failedMock).toHaveBeenCalledTimes(0); +}); + +test("save with missing input outputs warning", async () => { + const logWarningMock = jest.spyOn(actionUtils, "logWarning"); + const failedMock = jest.spyOn(core, "setFailed"); + + const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; + const savedCacheKey = "Linux-node-"; + + jest.spyOn(core, "getState") + // Cache Entry State + .mockImplementationOnce(() => { + return savedCacheKey; + }) + // Cache Key State + .mockImplementationOnce(() => { + return primaryKey; + }); + const saveCacheMock = jest.spyOn(cache, "saveCache"); + + await run(new StateProvider()); + + expect(saveCacheMock).toHaveBeenCalledTimes(0); + expect(logWarningMock).toHaveBeenCalledWith( + "Input required and not supplied: path" + ); + expect(logWarningMock).toHaveBeenCalledTimes(1); + expect(failedMock).toHaveBeenCalledTimes(0); +}); + +test("save with large cache outputs warning", async () => { + const logWarningMock = jest.spyOn(actionUtils, "logWarning"); + const failedMock = jest.spyOn(core, "setFailed"); + + const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; + const savedCacheKey = "Linux-node-"; + + jest.spyOn(core, "getState") + // Cache Entry State + .mockImplementationOnce(() => { + return savedCacheKey; + }) + // Cache Key State + .mockImplementationOnce(() => { + return primaryKey; + }); + + const inputPath = "node_modules"; + testUtils.setInput(Inputs.Path, inputPath); + + const saveCacheMock = jest + .spyOn(cache, "saveCache") + .mockImplementationOnce(() => { + throw new Error( + "Cache size of ~6144 MB (6442450944 B) is over the 5GB limit, not saving cache." + ); + }); + + await run(new StateProvider()); + + expect(saveCacheMock).toHaveBeenCalledTimes(1); + expect(saveCacheMock).toHaveBeenCalledWith( + [inputPath], + primaryKey, + expect.anything() + ); + + expect(logWarningMock).toHaveBeenCalledTimes(1); + expect(logWarningMock).toHaveBeenCalledWith( + "Cache size of ~6144 MB (6442450944 B) is over the 5GB limit, not saving cache." + ); + expect(failedMock).toHaveBeenCalledTimes(0); +}); + +test("save with reserve cache failure outputs warning", async () => { + const logWarningMock = jest.spyOn(actionUtils, "logWarning"); + const failedMock = jest.spyOn(core, "setFailed"); + + const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; + const savedCacheKey = "Linux-node-"; + + jest.spyOn(core, "getState") + // Cache Entry State + .mockImplementationOnce(() => { + return savedCacheKey; + }) + // Cache Key State + .mockImplementationOnce(() => { + return primaryKey; + }); + + const inputPath = "node_modules"; + testUtils.setInput(Inputs.Path, inputPath); + + const saveCacheMock = jest + .spyOn(cache, "saveCache") + .mockImplementationOnce(() => { + const actualCache = jest.requireActual("@actions/cache"); + const error = new actualCache.ReserveCacheError( + `Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.` + ); + throw error; + }); + + await run(new StateProvider()); + + expect(saveCacheMock).toHaveBeenCalledTimes(1); + expect(saveCacheMock).toHaveBeenCalledWith( + [inputPath], + primaryKey, + expect.anything() + ); + + expect(logWarningMock).toHaveBeenCalledWith( + `Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.` + ); + expect(logWarningMock).toHaveBeenCalledTimes(1); + expect(failedMock).toHaveBeenCalledTimes(0); +}); + +test("save with server error outputs warning", async () => { + const logWarningMock = jest.spyOn(actionUtils, "logWarning"); + const failedMock = jest.spyOn(core, "setFailed"); + + const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; + const savedCacheKey = "Linux-node-"; + + jest.spyOn(core, "getState") + // Cache Entry State + .mockImplementationOnce(() => { + return savedCacheKey; + }) + // Cache Key State + .mockImplementationOnce(() => { + return primaryKey; + }); + + const inputPath = "node_modules"; + testUtils.setInput(Inputs.Path, inputPath); + + const saveCacheMock = jest + .spyOn(cache, "saveCache") + .mockImplementationOnce(() => { + throw new Error("HTTP Error Occurred"); + }); + + await run(new StateProvider()); + + expect(saveCacheMock).toHaveBeenCalledTimes(1); + expect(saveCacheMock).toHaveBeenCalledWith( + [inputPath], + primaryKey, + expect.anything() + ); + + expect(logWarningMock).toHaveBeenCalledTimes(1); + expect(logWarningMock).toHaveBeenCalledWith("HTTP Error Occurred"); + + expect(failedMock).toHaveBeenCalledTimes(0); +}); + +test("save with valid inputs uploads a cache", async () => { + const failedMock = jest.spyOn(core, "setFailed"); + + const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; + const savedCacheKey = "Linux-node-"; + + jest.spyOn(core, "getState") + // Cache Entry State + .mockImplementationOnce(() => { + return savedCacheKey; + }) + // Cache Key State + .mockImplementationOnce(() => { + return primaryKey; + }); + + const inputPath = "node_modules"; + testUtils.setInput(Inputs.Path, inputPath); + testUtils.setInput(Inputs.UploadChunkSize, "4000000"); + + const cacheId = 4; + const saveCacheMock = jest + .spyOn(cache, "saveCache") + .mockImplementationOnce(() => { + return Promise.resolve(cacheId); + }); + + await run(new StateProvider()); + + expect(saveCacheMock).toHaveBeenCalledTimes(1); + expect(saveCacheMock).toHaveBeenCalledWith([inputPath], primaryKey, { + uploadChunkSize: 4000000 + }); + + expect(failedMock).toHaveBeenCalledTimes(0); +}); diff --git a/__tests__/saveOnly.test.ts b/__tests__/saveOnly.test.ts index 3c550e0..5604e3a 100644 --- a/__tests__/saveOnly.test.ts +++ b/__tests__/saveOnly.test.ts @@ -2,7 +2,7 @@ import * as cache from "@actions/cache"; import * as core from "@actions/core"; import { Events, Inputs, RefKey } from "../src/constants"; -import run from "../src/save"; +import run from "../src/saveOnly"; import * as actionUtils from "../src/utils/actionUtils"; import * as testUtils from "../src/utils/testUtils"; @@ -61,311 +61,13 @@ afterEach(() => { delete process.env[RefKey]; }); -test("save with invalid event outputs warning", async () => { - const logWarningMock = jest.spyOn(actionUtils, "logWarning"); - const failedMock = jest.spyOn(core, "setFailed"); - const invalidEvent = "commit_comment"; - process.env[Events.Key] = invalidEvent; - delete process.env[RefKey]; - await run(); - expect(logWarningMock).toHaveBeenCalledWith( - `Event Validation Error: The event type ${invalidEvent} is not supported because it's not tied to a branch or tag ref.` - ); - expect(failedMock).toHaveBeenCalledTimes(0); -}); - -test("save with no primary key in state outputs warning", async () => { - const logWarningMock = jest.spyOn(actionUtils, "logWarning"); - const failedMock = jest.spyOn(core, "setFailed"); - - const savedCacheKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; - jest.spyOn(core, "getState") - // Cache Entry State - .mockImplementationOnce(() => { - return ""; - }) - // Cache Key State - .mockImplementationOnce(() => { - return savedCacheKey; - }); - const saveCacheMock = jest.spyOn(cache, "saveCache"); - - await run(); - - expect(saveCacheMock).toHaveBeenCalledTimes(0); - expect(logWarningMock).toHaveBeenCalledWith( - `Error retrieving key from state.` - ); - expect(logWarningMock).toHaveBeenCalledTimes(1); - expect(failedMock).toHaveBeenCalledTimes(0); -}); - -test("save without AC available should no-op", async () => { - jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation( - () => false - ); - - const saveCacheMock = jest.spyOn(cache, "saveCache"); - - await run(); - - expect(saveCacheMock).toHaveBeenCalledTimes(0); -}); - -test("save on ghes without AC available should no-op", async () => { - jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true); - jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation( - () => false - ); - - const saveCacheMock = jest.spyOn(cache, "saveCache"); - - await run(); - - expect(saveCacheMock).toHaveBeenCalledTimes(0); -}); - -test("save on GHES with AC available", async () => { - jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true); - const failedMock = jest.spyOn(core, "setFailed"); - - const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; - const savedCacheKey = "Linux-node-"; - - jest.spyOn(core, "getState") - // Cache Entry State - .mockImplementationOnce(() => { - return savedCacheKey; - }) - // Cache Key State - .mockImplementationOnce(() => { - return primaryKey; - }); - - const inputPath = "node_modules"; - testUtils.setInput(Inputs.Path, inputPath); - testUtils.setInput(Inputs.UploadChunkSize, "4000000"); - - const cacheId = 4; - const saveCacheMock = jest - .spyOn(cache, "saveCache") - .mockImplementationOnce(() => { - return Promise.resolve(cacheId); - }); - - await run(); - - expect(saveCacheMock).toHaveBeenCalledTimes(1); - expect(saveCacheMock).toHaveBeenCalledWith([inputPath], primaryKey, { - uploadChunkSize: 4000000 - }); - - expect(failedMock).toHaveBeenCalledTimes(0); -}); - -test("save with exact match returns early", async () => { - const infoMock = jest.spyOn(core, "info"); - const failedMock = jest.spyOn(core, "setFailed"); - - const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; - const savedCacheKey = primaryKey; - - jest.spyOn(core, "getState") - // Cache Entry State - .mockImplementationOnce(() => { - return savedCacheKey; - }) - // Cache Key State - .mockImplementationOnce(() => { - return primaryKey; - }); - const saveCacheMock = jest.spyOn(cache, "saveCache"); - - await run(); - - expect(saveCacheMock).toHaveBeenCalledTimes(0); - expect(infoMock).toHaveBeenCalledWith( - `Cache hit occurred on the primary key ${primaryKey}, not saving cache.` - ); - expect(failedMock).toHaveBeenCalledTimes(0); -}); - -test("save with missing input outputs warning", async () => { - const logWarningMock = jest.spyOn(actionUtils, "logWarning"); - const failedMock = jest.spyOn(core, "setFailed"); - - const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; - const savedCacheKey = "Linux-node-"; - - jest.spyOn(core, "getState") - // Cache Entry State - .mockImplementationOnce(() => { - return savedCacheKey; - }) - // Cache Key State - .mockImplementationOnce(() => { - return primaryKey; - }); - const saveCacheMock = jest.spyOn(cache, "saveCache"); - - await run(); - - expect(saveCacheMock).toHaveBeenCalledTimes(0); - expect(logWarningMock).toHaveBeenCalledWith( - "Input required and not supplied: path" - ); - expect(logWarningMock).toHaveBeenCalledTimes(1); - expect(failedMock).toHaveBeenCalledTimes(0); -}); - -test("save with large cache outputs warning", async () => { - const logWarningMock = jest.spyOn(actionUtils, "logWarning"); - const failedMock = jest.spyOn(core, "setFailed"); - - const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; - const savedCacheKey = "Linux-node-"; - - jest.spyOn(core, "getState") - // Cache Entry State - .mockImplementationOnce(() => { - return savedCacheKey; - }) - // Cache Key State - .mockImplementationOnce(() => { - return primaryKey; - }); - - const inputPath = "node_modules"; - testUtils.setInput(Inputs.Path, inputPath); - - const saveCacheMock = jest - .spyOn(cache, "saveCache") - .mockImplementationOnce(() => { - throw new Error( - "Cache size of ~6144 MB (6442450944 B) is over the 5GB limit, not saving cache." - ); - }); - - await run(); - - expect(saveCacheMock).toHaveBeenCalledTimes(1); - expect(saveCacheMock).toHaveBeenCalledWith( - [inputPath], - primaryKey, - expect.anything() - ); - - expect(logWarningMock).toHaveBeenCalledTimes(1); - expect(logWarningMock).toHaveBeenCalledWith( - "Cache size of ~6144 MB (6442450944 B) is over the 5GB limit, not saving cache." - ); - expect(failedMock).toHaveBeenCalledTimes(0); -}); - -test("save with reserve cache failure outputs warning", async () => { - const logWarningMock = jest.spyOn(actionUtils, "logWarning"); - const failedMock = jest.spyOn(core, "setFailed"); - - const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; - const savedCacheKey = "Linux-node-"; - - jest.spyOn(core, "getState") - // Cache Entry State - .mockImplementationOnce(() => { - return savedCacheKey; - }) - // Cache Key State - .mockImplementationOnce(() => { - return primaryKey; - }); - - const inputPath = "node_modules"; - testUtils.setInput(Inputs.Path, inputPath); - - const saveCacheMock = jest - .spyOn(cache, "saveCache") - .mockImplementationOnce(() => { - const actualCache = jest.requireActual("@actions/cache"); - const error = new actualCache.ReserveCacheError( - `Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.` - ); - throw error; - }); - - await run(); - - expect(saveCacheMock).toHaveBeenCalledTimes(1); - expect(saveCacheMock).toHaveBeenCalledWith( - [inputPath], - primaryKey, - expect.anything() - ); - - expect(logWarningMock).toHaveBeenCalledWith( - `Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.` - ); - expect(logWarningMock).toHaveBeenCalledTimes(1); - expect(failedMock).toHaveBeenCalledTimes(0); -}); - -test("save with server error outputs warning", async () => { - const logWarningMock = jest.spyOn(actionUtils, "logWarning"); - const failedMock = jest.spyOn(core, "setFailed"); - - const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; - const savedCacheKey = "Linux-node-"; - - jest.spyOn(core, "getState") - // Cache Entry State - .mockImplementationOnce(() => { - return savedCacheKey; - }) - // Cache Key State - .mockImplementationOnce(() => { - return primaryKey; - }); - - const inputPath = "node_modules"; - testUtils.setInput(Inputs.Path, inputPath); - - const saveCacheMock = jest - .spyOn(cache, "saveCache") - .mockImplementationOnce(() => { - throw new Error("HTTP Error Occurred"); - }); - - await run(); - - expect(saveCacheMock).toHaveBeenCalledTimes(1); - expect(saveCacheMock).toHaveBeenCalledWith( - [inputPath], - primaryKey, - expect.anything() - ); - - expect(logWarningMock).toHaveBeenCalledTimes(1); - expect(logWarningMock).toHaveBeenCalledWith("HTTP Error Occurred"); - - expect(failedMock).toHaveBeenCalledTimes(0); -}); - test("save with valid inputs uploads a cache", async () => { const failedMock = jest.spyOn(core, "setFailed"); const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; - const savedCacheKey = "Linux-node-"; - - jest.spyOn(core, "getState") - // Cache Entry State - .mockImplementationOnce(() => { - return savedCacheKey; - }) - // Cache Key State - .mockImplementationOnce(() => { - return primaryKey; - }); const inputPath = "node_modules"; + testUtils.setInput(Inputs.Key, primaryKey); testUtils.setInput(Inputs.Path, inputPath); testUtils.setInput(Inputs.UploadChunkSize, "4000000"); diff --git a/dist/restore-only/index.js b/dist/restore-only/index.js index b646cbf..961c8a6 100644 --- a/dist/restore-only/index.js +++ b/dist/restore-only/index.js @@ -4943,20 +4943,19 @@ exports.checkBypass = checkBypass; "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.RefKey = exports.Events = exports.State = exports.Outputs = exports.Inputs = void 0; +exports.stateToOutputMap = exports.RefKey = exports.Events = exports.State = exports.Outputs = exports.Inputs = void 0; var Inputs; (function (Inputs) { Inputs["Key"] = "key"; Inputs["Path"] = "path"; Inputs["RestoreKeys"] = "restore-keys"; - Inputs["UploadChunkSize"] = "upload-chunk-size"; - Inputs["RestoredKey"] = "restored-key"; // Input from save action + Inputs["UploadChunkSize"] = "upload-chunk-size"; // Input for cache, save action })(Inputs = exports.Inputs || (exports.Inputs = {})); var Outputs; (function (Outputs) { Outputs["CacheHit"] = "cache-hit"; - Outputs["InputtedKey"] = "inputted-key"; - Outputs["MatchedKey"] = "matched-key"; // Output from restore action + Outputs["CachePrimaryKey"] = "cache-primary-key"; + Outputs["CacheRestoreKey"] = "cache-restore-key"; // Output from restore action })(Outputs = exports.Outputs || (exports.Outputs = {})); var State; (function (State) { @@ -4970,6 +4969,10 @@ var Events; Events["PullRequest"] = "pull_request"; })(Events = exports.Events || (exports.Events = {})); exports.RefKey = "GITHUB_REF"; +exports.stateToOutputMap = new Map([ + [State.CacheMatchedKey, Outputs.CacheRestoreKey], + [State.CachePrimaryKey, Outputs.CachePrimaryKey] +]); /***/ }), @@ -9361,7 +9364,7 @@ const constants_1 = __webpack_require__(196); class StateProviderBase { constructor() { // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function - this.setState = (key, value, outputKey) => { }; + this.setState = (key, value) => { }; // eslint-disable-next-line @typescript-eslint/no-unused-vars this.getState = (key) => ""; } @@ -9385,10 +9388,8 @@ exports.StateProvider = StateProvider; class NullStateProvider extends StateProviderBase { constructor() { super(...arguments); - this.setState = (key, value, outputKey) => { - if (outputKey) { - core.setOutput(outputKey, value); - } + this.setState = (key, value) => { + core.setOutput(constants_1.stateToOutputMap.get(key), value); }; // eslint-disable-next-line @typescript-eslint/no-unused-vars this.getState = (key) => ""; @@ -50467,7 +50468,7 @@ function restoreImpl(stateProvider) { return; } const primaryKey = core.getInput(constants_1.Inputs.Key, { required: true }); - stateProvider.setState(constants_1.State.CachePrimaryKey, primaryKey, constants_1.Outputs.InputtedKey); + stateProvider.setState(constants_1.State.CachePrimaryKey, primaryKey); const restoreKeys = utils.getInputAsArray(constants_1.Inputs.RestoreKeys); const cachePaths = utils.getInputAsArray(constants_1.Inputs.Path, { required: true @@ -50481,7 +50482,7 @@ function restoreImpl(stateProvider) { return; } // Store the matched cache key in states - stateProvider.setState(constants_1.State.CacheMatchedKey, cacheKey, constants_1.Outputs.MatchedKey); + stateProvider.setState(constants_1.State.CacheMatchedKey, cacheKey); const isExactKeyMatch = utils.isExactKeyMatch(core.getInput(constants_1.Inputs.Key, { required: true }), cacheKey); core.setOutput(constants_1.Outputs.CacheHit, isExactKeyMatch.toString()); core.info(`Cache restored from key: ${cacheKey}`); diff --git a/dist/restore/index.js b/dist/restore/index.js index 18e2e92..af749dc 100644 --- a/dist/restore/index.js +++ b/dist/restore/index.js @@ -4943,20 +4943,19 @@ exports.checkBypass = checkBypass; "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.RefKey = exports.Events = exports.State = exports.Outputs = exports.Inputs = void 0; +exports.stateToOutputMap = exports.RefKey = exports.Events = exports.State = exports.Outputs = exports.Inputs = void 0; var Inputs; (function (Inputs) { Inputs["Key"] = "key"; Inputs["Path"] = "path"; Inputs["RestoreKeys"] = "restore-keys"; - Inputs["UploadChunkSize"] = "upload-chunk-size"; - Inputs["RestoredKey"] = "restored-key"; // Input from save action + Inputs["UploadChunkSize"] = "upload-chunk-size"; // Input for cache, save action })(Inputs = exports.Inputs || (exports.Inputs = {})); var Outputs; (function (Outputs) { Outputs["CacheHit"] = "cache-hit"; - Outputs["InputtedKey"] = "inputted-key"; - Outputs["MatchedKey"] = "matched-key"; // Output from restore action + Outputs["CachePrimaryKey"] = "cache-primary-key"; + Outputs["CacheRestoreKey"] = "cache-restore-key"; // Output from restore action })(Outputs = exports.Outputs || (exports.Outputs = {})); var State; (function (State) { @@ -4970,6 +4969,10 @@ var Events; Events["PullRequest"] = "pull_request"; })(Events = exports.Events || (exports.Events = {})); exports.RefKey = "GITHUB_REF"; +exports.stateToOutputMap = new Map([ + [State.CacheMatchedKey, Outputs.CacheRestoreKey], + [State.CachePrimaryKey, Outputs.CachePrimaryKey] +]); /***/ }), @@ -9361,7 +9364,7 @@ const constants_1 = __webpack_require__(196); class StateProviderBase { constructor() { // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function - this.setState = (key, value, outputKey) => { }; + this.setState = (key, value) => { }; // eslint-disable-next-line @typescript-eslint/no-unused-vars this.getState = (key) => ""; } @@ -9385,10 +9388,8 @@ exports.StateProvider = StateProvider; class NullStateProvider extends StateProviderBase { constructor() { super(...arguments); - this.setState = (key, value, outputKey) => { - if (outputKey) { - core.setOutput(outputKey, value); - } + this.setState = (key, value) => { + core.setOutput(constants_1.stateToOutputMap.get(key), value); }; // eslint-disable-next-line @typescript-eslint/no-unused-vars this.getState = (key) => ""; @@ -50467,7 +50468,7 @@ function restoreImpl(stateProvider) { return; } const primaryKey = core.getInput(constants_1.Inputs.Key, { required: true }); - stateProvider.setState(constants_1.State.CachePrimaryKey, primaryKey, constants_1.Outputs.InputtedKey); + stateProvider.setState(constants_1.State.CachePrimaryKey, primaryKey); const restoreKeys = utils.getInputAsArray(constants_1.Inputs.RestoreKeys); const cachePaths = utils.getInputAsArray(constants_1.Inputs.Path, { required: true @@ -50481,7 +50482,7 @@ function restoreImpl(stateProvider) { return; } // Store the matched cache key in states - stateProvider.setState(constants_1.State.CacheMatchedKey, cacheKey, constants_1.Outputs.MatchedKey); + stateProvider.setState(constants_1.State.CacheMatchedKey, cacheKey); const isExactKeyMatch = utils.isExactKeyMatch(core.getInput(constants_1.Inputs.Key, { required: true }), cacheKey); core.setOutput(constants_1.Outputs.CacheHit, isExactKeyMatch.toString()); core.info(`Cache restored from key: ${cacheKey}`); diff --git a/dist/save-only/index.js b/dist/save-only/index.js index 9c29969..96dd341 100644 --- a/dist/save-only/index.js +++ b/dist/save-only/index.js @@ -4972,20 +4972,19 @@ exports.checkBypass = checkBypass; "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.RefKey = exports.Events = exports.State = exports.Outputs = exports.Inputs = void 0; +exports.stateToOutputMap = exports.RefKey = exports.Events = exports.State = exports.Outputs = exports.Inputs = void 0; var Inputs; (function (Inputs) { Inputs["Key"] = "key"; Inputs["Path"] = "path"; Inputs["RestoreKeys"] = "restore-keys"; - Inputs["UploadChunkSize"] = "upload-chunk-size"; - Inputs["RestoredKey"] = "restored-key"; // Input from save action + Inputs["UploadChunkSize"] = "upload-chunk-size"; // Input for cache, save action })(Inputs = exports.Inputs || (exports.Inputs = {})); var Outputs; (function (Outputs) { Outputs["CacheHit"] = "cache-hit"; - Outputs["InputtedKey"] = "inputted-key"; - Outputs["MatchedKey"] = "matched-key"; // Output from restore action + Outputs["CachePrimaryKey"] = "cache-primary-key"; + Outputs["CacheRestoreKey"] = "cache-restore-key"; // Output from restore action })(Outputs = exports.Outputs || (exports.Outputs = {})); var State; (function (State) { @@ -4999,6 +4998,10 @@ var Events; Events["PullRequest"] = "pull_request"; })(Events = exports.Events || (exports.Events = {})); exports.RefKey = "GITHUB_REF"; +exports.stateToOutputMap = new Map([ + [State.CacheMatchedKey, Outputs.CacheRestoreKey], + [State.CachePrimaryKey, Outputs.CachePrimaryKey] +]); /***/ }), @@ -9390,7 +9393,7 @@ const constants_1 = __webpack_require__(196); class StateProviderBase { constructor() { // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function - this.setState = (key, value, outputKey) => { }; + this.setState = (key, value) => { }; // eslint-disable-next-line @typescript-eslint/no-unused-vars this.getState = (key) => ""; } @@ -9414,10 +9417,8 @@ exports.StateProvider = StateProvider; class NullStateProvider extends StateProviderBase { constructor() { super(...arguments); - this.setState = (key, value, outputKey) => { - if (outputKey) { - core.setOutput(outputKey, value); - } + this.setState = (key, value) => { + core.setOutput(constants_1.stateToOutputMap.get(key), value); }; // eslint-disable-next-line @typescript-eslint/no-unused-vars this.getState = (key) => ""; @@ -41111,7 +41112,7 @@ function saveImpl(stateProvider) { } // If matched restore key is same as primary key, then do not save cache // NO-OP in case of SaveOnly action - const restoredKey = stateProvider.getCacheState() || core.getInput(constants_1.Inputs.RestoredKey); + const restoredKey = stateProvider.getCacheState(); if (utils.isExactKeyMatch(primaryKey, restoredKey)) { core.info(`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`); return; diff --git a/dist/save/index.js b/dist/save/index.js index 69ea907..3032065 100644 --- a/dist/save/index.js +++ b/dist/save/index.js @@ -4943,20 +4943,19 @@ exports.checkBypass = checkBypass; "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.RefKey = exports.Events = exports.State = exports.Outputs = exports.Inputs = void 0; +exports.stateToOutputMap = exports.RefKey = exports.Events = exports.State = exports.Outputs = exports.Inputs = void 0; var Inputs; (function (Inputs) { Inputs["Key"] = "key"; Inputs["Path"] = "path"; Inputs["RestoreKeys"] = "restore-keys"; - Inputs["UploadChunkSize"] = "upload-chunk-size"; - Inputs["RestoredKey"] = "restored-key"; // Input from save action + Inputs["UploadChunkSize"] = "upload-chunk-size"; // Input for cache, save action })(Inputs = exports.Inputs || (exports.Inputs = {})); var Outputs; (function (Outputs) { Outputs["CacheHit"] = "cache-hit"; - Outputs["InputtedKey"] = "inputted-key"; - Outputs["MatchedKey"] = "matched-key"; // Output from restore action + Outputs["CachePrimaryKey"] = "cache-primary-key"; + Outputs["CacheRestoreKey"] = "cache-restore-key"; // Output from restore action })(Outputs = exports.Outputs || (exports.Outputs = {})); var State; (function (State) { @@ -4970,6 +4969,10 @@ var Events; Events["PullRequest"] = "pull_request"; })(Events = exports.Events || (exports.Events = {})); exports.RefKey = "GITHUB_REF"; +exports.stateToOutputMap = new Map([ + [State.CacheMatchedKey, Outputs.CacheRestoreKey], + [State.CachePrimaryKey, Outputs.CachePrimaryKey] +]); /***/ }), @@ -9361,7 +9364,7 @@ const constants_1 = __webpack_require__(196); class StateProviderBase { constructor() { // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function - this.setState = (key, value, outputKey) => { }; + this.setState = (key, value) => { }; // eslint-disable-next-line @typescript-eslint/no-unused-vars this.getState = (key) => ""; } @@ -9385,10 +9388,8 @@ exports.StateProvider = StateProvider; class NullStateProvider extends StateProviderBase { constructor() { super(...arguments); - this.setState = (key, value, outputKey) => { - if (outputKey) { - core.setOutput(outputKey, value); - } + this.setState = (key, value) => { + core.setOutput(constants_1.stateToOutputMap.get(key), value); }; // eslint-disable-next-line @typescript-eslint/no-unused-vars this.getState = (key) => ""; @@ -41082,7 +41083,7 @@ function saveImpl(stateProvider) { } // If matched restore key is same as primary key, then do not save cache // NO-OP in case of SaveOnly action - const restoredKey = stateProvider.getCacheState() || core.getInput(constants_1.Inputs.RestoredKey); + const restoredKey = stateProvider.getCacheState(); if (utils.isExactKeyMatch(primaryKey, restoredKey)) { core.info(`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`); return; diff --git a/restore/action.yml b/restore/action.yml index 4729458..ef5681e 100644 --- a/restore/action.yml +++ b/restore/action.yml @@ -14,9 +14,9 @@ inputs: outputs: cache-hit: description: 'A boolean value to indicate an exact match was found for the primary key' - inputted-key: - description: 'Key passed in the input to use in subsequent steps of the workflow' - matched-key: + cache-primary-key: + description: 'Cache primary key passed in the input to use in subsequent steps of the workflow' + cache-restore-key: description: 'Cache key restored' runs: using: 'node16' diff --git a/save/action.yml b/save/action.yml index 220de82..85414eb 100644 --- a/save/action.yml +++ b/save/action.yml @@ -11,9 +11,6 @@ inputs: upload-chunk-size: description: 'The chunk size used to split up large files during upload, in bytes' required: false - restored-key: - description: 'Cache key restored from the restore action' - required: false runs: using: 'node16' main: '../dist/save-only/index.js' diff --git a/src/constants.ts b/src/constants.ts index 69d6379..091b2a1 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,15 +1,14 @@ export enum Inputs { - Key = "key", // Input from cache, restore, save action - Path = "path", // Input from cache, restore, save action - RestoreKeys = "restore-keys", // Input from cache, restore action - UploadChunkSize = "upload-chunk-size", // Input from cache, save action - RestoredKey = "restored-key" // Input from save action + Key = "key", // Input for cache, restore, save action + Path = "path", // Input for cache, restore, save action + RestoreKeys = "restore-keys", // Input for cache, restore action + UploadChunkSize = "upload-chunk-size" // Input for cache, save action } export enum Outputs { CacheHit = "cache-hit", // Output from cache, restore action - InputtedKey = "inputted-key", // Output from restore action - MatchedKey = "matched-key" // Output from restore action + CachePrimaryKey = "cache-primary-key", // Output from restore action + CacheRestoreKey = "cache-restore-key" // Output from restore action } export enum State { @@ -24,3 +23,8 @@ export enum Events { } export const RefKey = "GITHUB_REF"; + +export const stateToOutputMap = new Map([ + [State.CacheMatchedKey, Outputs.CacheRestoreKey], + [State.CachePrimaryKey, Outputs.CachePrimaryKey] +]); diff --git a/src/restoreImpl.ts b/src/restoreImpl.ts index 3a57f90..dec2437 100644 --- a/src/restoreImpl.ts +++ b/src/restoreImpl.ts @@ -25,11 +25,7 @@ async function restoreImpl( } const primaryKey = core.getInput(Inputs.Key, { required: true }); - stateProvider.setState( - State.CachePrimaryKey, - primaryKey, - Outputs.InputtedKey - ); + stateProvider.setState(State.CachePrimaryKey, primaryKey); const restoreKeys = utils.getInputAsArray(Inputs.RestoreKeys); const cachePaths = utils.getInputAsArray(Inputs.Path, { @@ -54,11 +50,7 @@ async function restoreImpl( } // Store the matched cache key in states - stateProvider.setState( - State.CacheMatchedKey, - cacheKey, - Outputs.MatchedKey - ); + stateProvider.setState(State.CacheMatchedKey, cacheKey); const isExactKeyMatch = utils.isExactKeyMatch( core.getInput(Inputs.Key, { required: true }), diff --git a/src/saveImpl.ts b/src/saveImpl.ts index 67534c7..4c02c24 100644 --- a/src/saveImpl.ts +++ b/src/saveImpl.ts @@ -42,8 +42,7 @@ async function saveImpl(stateProvider: IStateProvider): Promise { // If matched restore key is same as primary key, then do not save cache // NO-OP in case of SaveOnly action - const restoredKey = - stateProvider.getCacheState() || core.getInput(Inputs.RestoredKey); + const restoredKey = stateProvider.getCacheState(); if (utils.isExactKeyMatch(primaryKey, restoredKey)) { core.info( diff --git a/src/stateProvider.ts b/src/stateProvider.ts index 71cf33b..2578806 100644 --- a/src/stateProvider.ts +++ b/src/stateProvider.ts @@ -1,9 +1,9 @@ import * as core from "@actions/core"; -import { State } from "./constants"; +import { State, stateToOutputMap } from "./constants"; export interface IStateProvider { - setState(key: string, value: string, outputKey?: string): void; + setState(key: string, value: string): void; getState(key: string): string; getCacheState(): string | undefined; @@ -21,7 +21,7 @@ class StateProviderBase implements IStateProvider { } // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function - setState = (key: string, value: string, outputKey?: string) => {}; + setState = (key: string, value: string) => {}; // eslint-disable-next-line @typescript-eslint/no-unused-vars getState = (key: string) => ""; @@ -33,10 +33,8 @@ export class StateProvider extends StateProviderBase { } export class NullStateProvider extends StateProviderBase { - setState = (key: string, value: string, outputKey?: string) => { - if (outputKey) { - core.setOutput(outputKey, value); - } + setState = (key: string, value: string) => { + core.setOutput(stateToOutputMap.get(key) as string, value); }; // eslint-disable-next-line @typescript-eslint/no-unused-vars getState = (key: string) => "";