import * as cache from "@actions/cache"; import * as core from "@actions/core"; import { Events, RefKey } from "../src/constants"; import { restoreRun } from "../src/restoreImpl"; 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); } ); jest.spyOn(actionUtils, "getInputAsBool").mockImplementation( (name, options) => { const actualUtils = jest.requireActual("../src/utils/actionUtils"); return actualUtils.getInputAsBool(name, options); } ); }); beforeEach(() => { jest.restoreAllMocks(); 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 no cache found", async () => { const path = "node_modules"; const key = "node-test"; testUtils.setInputs({ path: path, key, enableCrossOsArchive: false }); 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 restoreRun(); expect(restoreCacheMock).toHaveBeenCalledTimes(1); expect(restoreCacheMock).toHaveBeenCalledWith( [path], key, [], { lookupOnly: false }, false ); expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); expect(stateMock).toHaveBeenCalledWith("CACHE_PATH", path); expect(stateMock).toHaveBeenCalledTimes(2); 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], enableCrossOsArchive: false }); 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 restoreRun(); expect(restoreCacheMock).toHaveBeenCalledTimes(1); expect(restoreCacheMock).toHaveBeenCalledWith( [path], key, [restoreKey], { lookupOnly: false }, false ); expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); expect(stateMock).toHaveBeenCalledWith("CACHE_PATH", path); expect(stateMock).toHaveBeenCalledTimes(2); 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, enableCrossOsArchive: false }); 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 restoreRun(); expect(restoreCacheMock).toHaveBeenCalledTimes(1); expect(restoreCacheMock).toHaveBeenCalledWith( [path], key, [], { lookupOnly: false }, false ); expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); expect(stateMock).toHaveBeenCalledWith("CACHE_RESULT", key); expect(stateMock).toHaveBeenCalledWith("CACHE_PATH", path); expect(stateMock).toHaveBeenCalledTimes(3); 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], enableCrossOsArchive: false }); 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 restoreRun(); expect(restoreCacheMock).toHaveBeenCalledTimes(1); expect(restoreCacheMock).toHaveBeenCalledWith( [path], key, [restoreKey], { lookupOnly: false }, false ); expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); expect(stateMock).toHaveBeenCalledWith("CACHE_RESULT", restoreKey); expect(stateMock).toHaveBeenCalledWith("CACHE_PATH", path); expect(stateMock).toHaveBeenCalledTimes(3); expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false"); expect(infoMock).toHaveBeenCalledWith( `Cache restored from key: ${restoreKey}` ); expect(failedMock).toHaveBeenCalledTimes(0); }); test("Fail restore when fail on cache miss is enabled and primary + restore keys not found", async () => { const path = "node_modules"; const key = "node-test"; const restoreKey = "node-"; testUtils.setInputs({ path: path, key, restoreKeys: [restoreKey], failOnCacheMiss: true }); 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(undefined); }); await restoreRun(); expect(restoreCacheMock).toHaveBeenCalledTimes(1); expect(restoreCacheMock).toHaveBeenCalledWith( [path], key, [restoreKey], { lookupOnly: false }, false ); expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); expect(setCacheHitOutputMock).toHaveBeenCalledTimes(0); expect(failedMock).toHaveBeenCalledWith( `Failed to restore cache entry. Exiting as fail-on-cache-miss is set. Input key: ${key}` ); expect(failedMock).toHaveBeenCalledTimes(1); }); test("restore when fail on cache miss is enabled and primary key doesn't match restored key", async () => { const path = "node_modules"; const key = "node-test"; const restoreKey = "node-"; testUtils.setInputs({ path: path, key, restoreKeys: [restoreKey], failOnCacheMiss: true }); 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 restoreRun(); expect(restoreCacheMock).toHaveBeenCalledTimes(1); expect(restoreCacheMock).toHaveBeenCalledWith( [path], key, [restoreKey], { lookupOnly: false }, false ); expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); expect(stateMock).toHaveBeenCalledWith("CACHE_RESULT", restoreKey); expect(stateMock).toHaveBeenCalledWith("CACHE_PATH", path); expect(stateMock).toHaveBeenCalledTimes(3); expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false"); expect(infoMock).toHaveBeenCalledWith( `Cache restored from key: ${restoreKey}` ); expect(failedMock).toHaveBeenCalledTimes(0); }); test("restore with fail on cache miss disabled and no cache found", async () => { const path = "node_modules"; const key = "node-test"; const restoreKey = "node-"; testUtils.setInputs({ path: path, key, restoreKeys: [restoreKey], failOnCacheMiss: false }); 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 restoreRun(); expect(restoreCacheMock).toHaveBeenCalledTimes(1); expect(restoreCacheMock).toHaveBeenCalledWith( [path], key, [restoreKey], { lookupOnly: false }, false ); expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); expect(stateMock).toHaveBeenCalledWith("CACHE_PATH", path); expect(stateMock).toHaveBeenCalledTimes(2); expect(infoMock).toHaveBeenCalledWith( `Cache not found for input keys: ${key}, ${restoreKey}` ); expect(failedMock).toHaveBeenCalledTimes(0); });