451 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			451 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | import * as core from "@actions/core"; | ||
|  | import * as exec from "@actions/exec"; | ||
|  | import * as io from "@actions/io"; | ||
|  | import * as path from "path"; | ||
|  | import * as cacheHttpClient from "../src/cacheHttpClient"; | ||
|  | import { Events, Inputs } from "../src/constants"; | ||
|  | import { ArtifactCacheEntry } from "../src/contracts"; | ||
|  | import run from "../src/restore"; | ||
|  | import * as actionUtils from "../src/utils/actionUtils"; | ||
|  | import * as testUtils from "../src/utils/testUtils"; | ||
|  | 
 | ||
|  | jest.mock("@actions/exec"); | ||
|  | jest.mock("@actions/io"); | ||
|  | jest.mock("../src/utils/actionUtils"); | ||
|  | jest.mock("../src/cacheHttpClient"); | ||
|  | 
 | ||
|  | beforeAll(() => { | ||
|  |     jest.spyOn(actionUtils, "resolvePath").mockImplementation(filePath => { | ||
|  |         return path.resolve(filePath); | ||
|  |     }); | ||
|  | 
 | ||
|  |     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, "getSupportedEvents").mockImplementation(() => { | ||
|  |         const actualUtils = jest.requireActual("../src/utils/actionUtils"); | ||
|  |         return actualUtils.getSupportedEvents(); | ||
|  |     }); | ||
|  | 
 | ||
|  |     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, pull_request events are supported at this time.` | ||
|  |     ); | ||
|  | }); | ||
|  | 
 | ||
|  | test("restore with no path should fail", async () => { | ||
|  |     const failedMock = jest.spyOn(core, "setFailed"); | ||
|  |     await run(); | ||
|  |     expect(failedMock).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"); | ||
|  |     await run(); | ||
|  |     expect(failedMock).toHaveBeenCalledWith( | ||
|  |         "Input required and not supplied: key" | ||
|  |     ); | ||
|  | }); | ||
|  | 
 | ||
|  | test("restore with too many keys should fail", async () => { | ||
|  |     const key = "node-test"; | ||
|  |     const restoreKeys = [...Array(20).keys()].map(x => x.toString()); | ||
|  |     testUtils.setInputs({ | ||
|  |         path: "node_modules", | ||
|  |         key, | ||
|  |         restoreKeys | ||
|  |     }); | ||
|  |     const failedMock = jest.spyOn(core, "setFailed"); | ||
|  |     await run(); | ||
|  |     expect(failedMock).toHaveBeenCalledWith( | ||
|  |         `Key Validation Error: Keys are limited to a maximum of 10.` | ||
|  |     ); | ||
|  | }); | ||
|  | 
 | ||
|  | test("restore with large key should fail", async () => { | ||
|  |     const key = "foo".repeat(512); // Over the 512 character limit
 | ||
|  |     testUtils.setInputs({ | ||
|  |         path: "node_modules", | ||
|  |         key | ||
|  |     }); | ||
|  |     const failedMock = jest.spyOn(core, "setFailed"); | ||
|  |     await run(); | ||
|  |     expect(failedMock).toHaveBeenCalledWith( | ||
|  |         `Key Validation Error: ${key} cannot be larger than 512 characters.` | ||
|  |     ); | ||
|  | }); | ||
|  | 
 | ||
|  | test("restore with invalid key should fail", async () => { | ||
|  |     const key = "comma,comma"; | ||
|  |     testUtils.setInputs({ | ||
|  |         path: "node_modules", | ||
|  |         key | ||
|  |     }); | ||
|  |     const failedMock = jest.spyOn(core, "setFailed"); | ||
|  |     await run(); | ||
|  |     expect(failedMock).toHaveBeenCalledWith( | ||
|  |         `Key Validation Error: ${key} cannot contain commas.` | ||
|  |     ); | ||
|  | }); | ||
|  | 
 | ||
|  | test("restore with no cache found", async () => { | ||
|  |     const key = "node-test"; | ||
|  |     testUtils.setInputs({ | ||
|  |         path: "node_modules", | ||
|  |         key | ||
|  |     }); | ||
|  | 
 | ||
|  |     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 clientMock = jest.spyOn(cacheHttpClient, "getCacheEntry"); | ||
|  |     clientMock.mockImplementation(() => { | ||
|  |         return Promise.resolve(null); | ||
|  |     }); | ||
|  | 
 | ||
|  |     await run(); | ||
|  | 
 | ||
|  |     expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); | ||
|  |     expect(warningMock).toHaveBeenCalledTimes(0); | ||
|  |     expect(failedMock).toHaveBeenCalledTimes(0); | ||
|  | 
 | ||
|  |     expect(infoMock).toHaveBeenCalledWith( | ||
|  |         `Cache not found for input keys: ${key}.` | ||
|  |     ); | ||
|  | }); | ||
|  | 
 | ||
|  | test("restore with server error should fail", async () => { | ||
|  |     const key = "node-test"; | ||
|  |     testUtils.setInputs({ | ||
|  |         path: "node_modules", | ||
|  |         key | ||
|  |     }); | ||
|  | 
 | ||
|  |     const warningMock = jest.spyOn(core, "warning"); | ||
|  |     const failedMock = jest.spyOn(core, "setFailed"); | ||
|  |     const stateMock = jest.spyOn(core, "saveState"); | ||
|  | 
 | ||
|  |     const clientMock = jest.spyOn(cacheHttpClient, "getCacheEntry"); | ||
|  |     clientMock.mockImplementation(() => { | ||
|  |         throw new Error("HTTP Error Occurred"); | ||
|  |     }); | ||
|  | 
 | ||
|  |     const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput"); | ||
|  | 
 | ||
|  |     await run(); | ||
|  | 
 | ||
|  |     expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); | ||
|  | 
 | ||
|  |     expect(warningMock).toHaveBeenCalledTimes(1); | ||
|  |     expect(warningMock).toHaveBeenCalledWith("HTTP Error Occurred"); | ||
|  | 
 | ||
|  |     expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); | ||
|  |     expect(setCacheHitOutputMock).toHaveBeenCalledWith(false); | ||
|  | 
 | ||
|  |     expect(failedMock).toHaveBeenCalledTimes(0); | ||
|  | }); | ||
|  | 
 | ||
|  | test("restore with restore keys and no cache found", async () => { | ||
|  |     const key = "node-test"; | ||
|  |     const restoreKey = "node-"; | ||
|  |     testUtils.setInputs({ | ||
|  |         path: "node_modules", | ||
|  |         key, | ||
|  |         restoreKeys: [restoreKey] | ||
|  |     }); | ||
|  | 
 | ||
|  |     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 clientMock = jest.spyOn(cacheHttpClient, "getCacheEntry"); | ||
|  |     clientMock.mockImplementation(() => { | ||
|  |         return Promise.resolve(null); | ||
|  |     }); | ||
|  | 
 | ||
|  |     await run(); | ||
|  | 
 | ||
|  |     expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); | ||
|  |     expect(warningMock).toHaveBeenCalledTimes(0); | ||
|  |     expect(failedMock).toHaveBeenCalledTimes(0); | ||
|  | 
 | ||
|  |     expect(infoMock).toHaveBeenCalledWith( | ||
|  |         `Cache not found for input keys: ${key}, ${restoreKey}.` | ||
|  |     ); | ||
|  | }); | ||
|  | 
 | ||
|  | test("restore with cache found", async () => { | ||
|  |     const key = "node-test"; | ||
|  |     const cachePath = path.resolve("node_modules"); | ||
|  |     testUtils.setInputs({ | ||
|  |         path: "node_modules", | ||
|  |         key | ||
|  |     }); | ||
|  | 
 | ||
|  |     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: "www.actionscache.test/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 args = IS_WINDOWS | ||
|  |         ? [ | ||
|  |               "-xz", | ||
|  |               "--force-local", | ||
|  |               "-f", | ||
|  |               archivePath.replace(/\\/g, "/"), | ||
|  |               "-C", | ||
|  |               cachePath.replace(/\\/g, "/") | ||
|  |           ] | ||
|  |         : ["-xz", "-f", archivePath, "-C", cachePath]; | ||
|  | 
 | ||
|  |     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 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: "www.actionscache.test/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 = 62915000; | ||
|  |     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(infoMock).toHaveBeenCalledWith(`Cache Size: ~60 MB (62915000 B)`); | ||
|  |     expect(mkdirMock).toHaveBeenCalledWith(cachePath); | ||
|  | 
 | ||
|  |     const IS_WINDOWS = process.platform === "win32"; | ||
|  |     const args = IS_WINDOWS | ||
|  |         ? [ | ||
|  |               "-xz", | ||
|  |               "--force-local", | ||
|  |               "-f", | ||
|  |               archivePath.replace(/\\/g, "/"), | ||
|  |               "-C", | ||
|  |               cachePath.replace(/\\/g, "/") | ||
|  |           ] | ||
|  |         : ["-xz", "-f", archivePath, "-C", cachePath]; | ||
|  | 
 | ||
|  |     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-"; | ||
|  |     const cachePath = path.resolve("node_modules"); | ||
|  |     testUtils.setInputs({ | ||
|  |         path: "node_modules", | ||
|  |         key, | ||
|  |         restoreKeys: [restoreKey] | ||
|  |     }); | ||
|  | 
 | ||
|  |     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: restoreKey, | ||
|  |         scope: "refs/heads/master", | ||
|  |         archiveLocation: "www.actionscache.test/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, restoreKey]); | ||
|  |     expect(setCacheStateMock).toHaveBeenCalledWith(cacheEntry); | ||
|  |     expect(createTempDirectoryMock).toHaveBeenCalledTimes(1); | ||
|  |     expect(downloadCacheMock).toHaveBeenCalledWith(cacheEntry, archivePath); | ||
|  |     expect(getArchiveFileSizeMock).toHaveBeenCalledWith(archivePath); | ||
|  |     expect(infoMock).toHaveBeenCalledWith(`Cache Size: ~0 MB (142 B)`); | ||
|  |     expect(mkdirMock).toHaveBeenCalledWith(cachePath); | ||
|  | 
 | ||
|  |     const IS_WINDOWS = process.platform === "win32"; | ||
|  |     const args = IS_WINDOWS | ||
|  |         ? [ | ||
|  |               "-xz", | ||
|  |               "--force-local", | ||
|  |               "-f", | ||
|  |               archivePath.replace(/\\/g, "/"), | ||
|  |               "-C", | ||
|  |               cachePath.replace(/\\/g, "/") | ||
|  |           ] | ||
|  |         : ["-xz", "-f", archivePath, "-C", cachePath]; | ||
|  | 
 | ||
|  |     expect(execMock).toHaveBeenCalledTimes(1); | ||
|  |     expect(execMock).toHaveBeenCalledWith(`"tar"`, args); | ||
|  | 
 | ||
|  |     expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); | ||
|  |     expect(setCacheHitOutputMock).toHaveBeenCalledWith(false); | ||
|  | 
 | ||
|  |     expect(infoMock).toHaveBeenCalledWith( | ||
|  |         `Cache restored from key: ${restoreKey}` | ||
|  |     ); | ||
|  |     expect(warningMock).toHaveBeenCalledTimes(0); | ||
|  |     expect(failedMock).toHaveBeenCalledTimes(0); | ||
|  | }); |