330 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			330 lines
		
	
	
		
			10 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 { Inputs } from "../src/constants"; | ||
|  | import { ArtifactCacheEntry } from "../src/contracts"; | ||
|  | import run from "../src/save"; | ||
|  | import * as actionUtils from "../src/utils/actionUtils"; | ||
|  | import * as testUtils from "../src/utils/testUtils"; | ||
|  | 
 | ||
|  | jest.mock("@actions/core"); | ||
|  | jest.mock("@actions/exec"); | ||
|  | jest.mock("@actions/io"); | ||
|  | jest.mock("../src/utils/actionUtils"); | ||
|  | jest.mock("../src/cacheHttpClient"); | ||
|  | 
 | ||
|  | beforeAll(() => { | ||
|  |     jest.spyOn(core, "getInput").mockImplementation((name, options) => { | ||
|  |         return jest.requireActual("@actions/core").getInput(name, options); | ||
|  |     }); | ||
|  | 
 | ||
|  |     jest.spyOn(actionUtils, "getCacheState").mockImplementation(() => { | ||
|  |         return jest.requireActual("../src/utils/actionUtils").getCacheState(); | ||
|  |     }); | ||
|  | 
 | ||
|  |     jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation( | ||
|  |         (key, cacheResult) => { | ||
|  |             return jest | ||
|  |                 .requireActual("../src/utils/actionUtils") | ||
|  |                 .isExactKeyMatch(key, cacheResult); | ||
|  |         } | ||
|  |     ); | ||
|  | 
 | ||
|  |     jest.spyOn(actionUtils, "resolvePath").mockImplementation(filePath => { | ||
|  |         return path.resolve(filePath); | ||
|  |     }); | ||
|  | 
 | ||
|  |     jest.spyOn(actionUtils, "createTempDirectory").mockImplementation(() => { | ||
|  |         return Promise.resolve("/foo/bar"); | ||
|  |     }); | ||
|  | 
 | ||
|  |     jest.spyOn(io, "which").mockImplementation(tool => { | ||
|  |         return Promise.resolve(tool); | ||
|  |     }); | ||
|  | }); | ||
|  | 
 | ||
|  | afterEach(() => { | ||
|  |     testUtils.clearInputs(); | ||
|  | }); | ||
|  | 
 | ||
|  | test("save with no primary key in state outputs warning", async () => { | ||
|  |     const warningMock = jest.spyOn(core, "warning"); | ||
|  |     const failedMock = jest.spyOn(core, "setFailed"); | ||
|  | 
 | ||
|  |     const cacheEntry: ArtifactCacheEntry = { | ||
|  |         cacheKey: "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43", | ||
|  |         scope: "refs/heads/master", | ||
|  |         creationTime: "2019-11-13T19:18:02+00:00", | ||
|  |         archiveLocation: "www.actionscache.test/download" | ||
|  |     }; | ||
|  | 
 | ||
|  |     jest.spyOn(core, "getState") | ||
|  |         // Cache Entry State
 | ||
|  |         .mockImplementationOnce(() => { | ||
|  |             return JSON.stringify(cacheEntry); | ||
|  |         }) | ||
|  |         // Cache Key State
 | ||
|  |         .mockImplementationOnce(() => { | ||
|  |             return ""; | ||
|  |         }); | ||
|  | 
 | ||
|  |     await run(); | ||
|  | 
 | ||
|  |     expect(warningMock).toHaveBeenCalledWith( | ||
|  |         `Error retrieving key from state.` | ||
|  |     ); | ||
|  |     expect(warningMock).toHaveBeenCalledTimes(1); | ||
|  |     expect(failedMock).toHaveBeenCalledTimes(0); | ||
|  | }); | ||
|  | 
 | ||
|  | test("save with exact match returns early", async () => { | ||
|  |     const infoMock = jest.spyOn(core, "info"); | ||
|  |     const warningMock = jest.spyOn(core, "warning"); | ||
|  |     const failedMock = jest.spyOn(core, "setFailed"); | ||
|  | 
 | ||
|  |     const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; | ||
|  |     const cacheEntry: ArtifactCacheEntry = { | ||
|  |         cacheKey: primaryKey, | ||
|  |         scope: "refs/heads/master", | ||
|  |         creationTime: "2019-11-13T19:18:02+00:00", | ||
|  |         archiveLocation: "www.actionscache.test/download" | ||
|  |     }; | ||
|  | 
 | ||
|  |     jest.spyOn(core, "getState") | ||
|  |         // Cache Entry State
 | ||
|  |         .mockImplementationOnce(() => { | ||
|  |             return JSON.stringify(cacheEntry); | ||
|  |         }) | ||
|  |         // Cache Key State
 | ||
|  |         .mockImplementationOnce(() => { | ||
|  |             return primaryKey; | ||
|  |         }); | ||
|  | 
 | ||
|  |     const execMock = jest.spyOn(exec, "exec"); | ||
|  | 
 | ||
|  |     await run(); | ||
|  | 
 | ||
|  |     expect(infoMock).toHaveBeenCalledWith( | ||
|  |         `Cache hit occurred on the primary key ${primaryKey}, not saving cache.` | ||
|  |     ); | ||
|  | 
 | ||
|  |     expect(execMock).toHaveBeenCalledTimes(0); | ||
|  | 
 | ||
|  |     expect(warningMock).toHaveBeenCalledTimes(0); | ||
|  |     expect(failedMock).toHaveBeenCalledTimes(0); | ||
|  | }); | ||
|  | 
 | ||
|  | test("save with missing input outputs warning", async () => { | ||
|  |     const warningMock = jest.spyOn(core, "warning"); | ||
|  |     const failedMock = jest.spyOn(core, "setFailed"); | ||
|  | 
 | ||
|  |     const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; | ||
|  |     const cacheEntry: ArtifactCacheEntry = { | ||
|  |         cacheKey: "Linux-node-", | ||
|  |         scope: "refs/heads/master", | ||
|  |         creationTime: "2019-11-13T19:18:02+00:00", | ||
|  |         archiveLocation: "www.actionscache.test/download" | ||
|  |     }; | ||
|  | 
 | ||
|  |     jest.spyOn(core, "getState") | ||
|  |         // Cache Entry State
 | ||
|  |         .mockImplementationOnce(() => { | ||
|  |             return JSON.stringify(cacheEntry); | ||
|  |         }) | ||
|  |         // Cache Key State
 | ||
|  |         .mockImplementationOnce(() => { | ||
|  |             return primaryKey; | ||
|  |         }); | ||
|  | 
 | ||
|  |     await run(); | ||
|  | 
 | ||
|  |     expect(warningMock).toHaveBeenCalledWith( | ||
|  |         "Input required and not supplied: path" | ||
|  |     ); | ||
|  |     expect(warningMock).toHaveBeenCalledTimes(1); | ||
|  |     expect(failedMock).toHaveBeenCalledTimes(0); | ||
|  | }); | ||
|  | 
 | ||
|  | test("save with large cache outputs warning", async () => { | ||
|  |     const warningMock = jest.spyOn(core, "warning"); | ||
|  |     const failedMock = jest.spyOn(core, "setFailed"); | ||
|  | 
 | ||
|  |     const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; | ||
|  |     const cacheEntry: ArtifactCacheEntry = { | ||
|  |         cacheKey: "Linux-node-", | ||
|  |         scope: "refs/heads/master", | ||
|  |         creationTime: "2019-11-13T19:18:02+00:00", | ||
|  |         archiveLocation: "www.actionscache.test/download" | ||
|  |     }; | ||
|  | 
 | ||
|  |     jest.spyOn(core, "getState") | ||
|  |         // Cache Entry State
 | ||
|  |         .mockImplementationOnce(() => { | ||
|  |             return JSON.stringify(cacheEntry); | ||
|  |         }) | ||
|  |         // Cache Key State
 | ||
|  |         .mockImplementationOnce(() => { | ||
|  |             return primaryKey; | ||
|  |         }); | ||
|  | 
 | ||
|  |     const inputPath = "node_modules"; | ||
|  |     const cachePath = path.resolve(inputPath); | ||
|  |     testUtils.setInput(Inputs.Path, inputPath); | ||
|  | 
 | ||
|  |     const execMock = jest.spyOn(exec, "exec"); | ||
|  | 
 | ||
|  |     const cacheSize = 1024 * 1024 * 1024; //~1GB, over the 400MB limit
 | ||
|  |     jest.spyOn(actionUtils, "getArchiveFileSize").mockImplementationOnce(() => { | ||
|  |         return cacheSize; | ||
|  |     }); | ||
|  | 
 | ||
|  |     await run(); | ||
|  | 
 | ||
|  |     const archivePath = path.join("/foo/bar", "cache.tgz"); | ||
|  | 
 | ||
|  |     const IS_WINDOWS = process.platform === "win32"; | ||
|  |     const args = IS_WINDOWS | ||
|  |         ? [ | ||
|  |               "-cz", | ||
|  |               "--force-local", | ||
|  |               "-f", | ||
|  |               archivePath.replace(/\\/g, "/"), | ||
|  |               "-C", | ||
|  |               cachePath.replace(/\\/g, "/"), | ||
|  |               "." | ||
|  |           ] | ||
|  |         : ["-cz", "-f", archivePath, "-C", cachePath, "."]; | ||
|  | 
 | ||
|  |     expect(execMock).toHaveBeenCalledTimes(1); | ||
|  |     expect(execMock).toHaveBeenCalledWith(`"tar"`, args); | ||
|  | 
 | ||
|  |     expect(warningMock).toHaveBeenCalledTimes(1); | ||
|  |     expect(warningMock).toHaveBeenCalledWith( | ||
|  |         "Cache size of ~1024 MB (1073741824 B) is over the 400MB limit, not saving cache." | ||
|  |     ); | ||
|  | 
 | ||
|  |     expect(failedMock).toHaveBeenCalledTimes(0); | ||
|  | }); | ||
|  | 
 | ||
|  | test("save with server error outputs warning", async () => { | ||
|  |     const warningMock = jest.spyOn(core, "warning"); | ||
|  |     const failedMock = jest.spyOn(core, "setFailed"); | ||
|  | 
 | ||
|  |     const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; | ||
|  |     const cacheEntry: ArtifactCacheEntry = { | ||
|  |         cacheKey: "Linux-node-", | ||
|  |         scope: "refs/heads/master", | ||
|  |         creationTime: "2019-11-13T19:18:02+00:00", | ||
|  |         archiveLocation: "www.actionscache.test/download" | ||
|  |     }; | ||
|  | 
 | ||
|  |     jest.spyOn(core, "getState") | ||
|  |         // Cache Entry State
 | ||
|  |         .mockImplementationOnce(() => { | ||
|  |             return JSON.stringify(cacheEntry); | ||
|  |         }) | ||
|  |         // Cache Key State
 | ||
|  |         .mockImplementationOnce(() => { | ||
|  |             return primaryKey; | ||
|  |         }); | ||
|  | 
 | ||
|  |     const inputPath = "node_modules"; | ||
|  |     const cachePath = path.resolve(inputPath); | ||
|  |     testUtils.setInput(Inputs.Path, inputPath); | ||
|  | 
 | ||
|  |     const execMock = jest.spyOn(exec, "exec"); | ||
|  | 
 | ||
|  |     const saveCacheMock = jest | ||
|  |         .spyOn(cacheHttpClient, "saveCache") | ||
|  |         .mockImplementationOnce(() => { | ||
|  |             throw new Error("HTTP Error Occurred"); | ||
|  |         }); | ||
|  | 
 | ||
|  |     await run(); | ||
|  | 
 | ||
|  |     const archivePath = path.join("/foo/bar", "cache.tgz"); | ||
|  | 
 | ||
|  |     const IS_WINDOWS = process.platform === "win32"; | ||
|  |     const args = IS_WINDOWS | ||
|  |         ? [ | ||
|  |               "-cz", | ||
|  |               "--force-local", | ||
|  |               "-f", | ||
|  |               archivePath.replace(/\\/g, "/"), | ||
|  |               "-C", | ||
|  |               cachePath.replace(/\\/g, "/"), | ||
|  |               "." | ||
|  |           ] | ||
|  |         : ["-cz", "-f", archivePath, "-C", cachePath, "."]; | ||
|  | 
 | ||
|  |     expect(execMock).toHaveBeenCalledTimes(1); | ||
|  |     expect(execMock).toHaveBeenCalledWith(`"tar"`, args); | ||
|  | 
 | ||
|  |     expect(saveCacheMock).toHaveBeenCalledTimes(1); | ||
|  |     expect(saveCacheMock).toHaveBeenCalledWith(primaryKey, archivePath); | ||
|  | 
 | ||
|  |     expect(warningMock).toHaveBeenCalledTimes(1); | ||
|  |     expect(warningMock).toHaveBeenCalledWith("HTTP Error Occurred"); | ||
|  | 
 | ||
|  |     expect(failedMock).toHaveBeenCalledTimes(0); | ||
|  | }); | ||
|  | 
 | ||
|  | test("save with valid inputs uploads a cache", async () => { | ||
|  |     const warningMock = jest.spyOn(core, "warning"); | ||
|  |     const failedMock = jest.spyOn(core, "setFailed"); | ||
|  | 
 | ||
|  |     const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; | ||
|  |     const cacheEntry: ArtifactCacheEntry = { | ||
|  |         cacheKey: "Linux-node-", | ||
|  |         scope: "refs/heads/master", | ||
|  |         creationTime: "2019-11-13T19:18:02+00:00", | ||
|  |         archiveLocation: "www.actionscache.test/download" | ||
|  |     }; | ||
|  | 
 | ||
|  |     jest.spyOn(core, "getState") | ||
|  |         // Cache Entry State
 | ||
|  |         .mockImplementationOnce(() => { | ||
|  |             return JSON.stringify(cacheEntry); | ||
|  |         }) | ||
|  |         // Cache Key State
 | ||
|  |         .mockImplementationOnce(() => { | ||
|  |             return primaryKey; | ||
|  |         }); | ||
|  | 
 | ||
|  |     const inputPath = "node_modules"; | ||
|  |     const cachePath = path.resolve(inputPath); | ||
|  |     testUtils.setInput(Inputs.Path, inputPath); | ||
|  | 
 | ||
|  |     const execMock = jest.spyOn(exec, "exec"); | ||
|  | 
 | ||
|  |     const saveCacheMock = jest.spyOn(cacheHttpClient, "saveCache"); | ||
|  | 
 | ||
|  |     await run(); | ||
|  | 
 | ||
|  |     const archivePath = path.join("/foo/bar", "cache.tgz"); | ||
|  | 
 | ||
|  |     const IS_WINDOWS = process.platform === "win32"; | ||
|  |     const args = IS_WINDOWS | ||
|  |         ? [ | ||
|  |               "-cz", | ||
|  |               "--force-local", | ||
|  |               "-f", | ||
|  |               archivePath.replace(/\\/g, "/"), | ||
|  |               "-C", | ||
|  |               cachePath.replace(/\\/g, "/"), | ||
|  |               "." | ||
|  |           ] | ||
|  |         : ["-cz", "-f", archivePath, "-C", cachePath, "."]; | ||
|  | 
 | ||
|  |     expect(execMock).toHaveBeenCalledTimes(1); | ||
|  |     expect(execMock).toHaveBeenCalledWith(`"tar"`, args); | ||
|  | 
 | ||
|  |     expect(saveCacheMock).toHaveBeenCalledTimes(1); | ||
|  |     expect(saveCacheMock).toHaveBeenCalledWith(primaryKey, archivePath); | ||
|  | 
 | ||
|  |     expect(warningMock).toHaveBeenCalledTimes(0); | ||
|  |     expect(failedMock).toHaveBeenCalledTimes(0); | ||
|  | }); |