diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts deleted file mode 100644 index 074a5e7..0000000 --- a/__tests__/main.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as core from "@actions/core"; - -import { Inputs } from "../src/constants"; -import run from "../src/restore"; -import * as testUtils from "../src/utils/testUtils"; - -test("restore with no path", 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" - ); -}); diff --git a/__tests__/restore.test.ts b/__tests__/restore.test.ts new file mode 100644 index 0000000..e854f6c --- /dev/null +++ b/__tests__/restore.test.ts @@ -0,0 +1,336 @@ +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/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(io, "which").mockImplementation(tool => { + return Promise.resolve(tool); + }); +}); +afterEach(() => { + testUtils.clearInputs(); +}); + +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 tarArchivePath = IS_WINDOWS + ? archivePath.replace(/\\/g, "/") + : archivePath; + const tarCachePath = IS_WINDOWS ? cachePath.replace(/\\/g, "/") : cachePath; + const args = IS_WINDOWS ? ["-xz", "--force-local"] : ["-xz"]; + args.push(...["-f", tarArchivePath, "-C", tarCachePath]); + + expect(execMock).toHaveBeenCalledTimes(1); + expect(execMock).toHaveBeenCalledWith(`"tar"`, args); + + expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); + expect(setCacheHitOutputMock).toHaveBeenCalledWith(true); + + expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`); + expect(warningMock).toHaveBeenCalledTimes(0); + expect(failedMock).toHaveBeenCalledTimes(0); +}); + +test("restore with cache found for restore key", async () => { + const key = "node-test"; + const restoreKey = "node-"; + 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(mkdirMock).toHaveBeenCalledWith(cachePath); + + const IS_WINDOWS = process.platform === "win32"; + const tarArchivePath = IS_WINDOWS + ? archivePath.replace(/\\/g, "/") + : archivePath; + const tarCachePath = IS_WINDOWS ? cachePath.replace(/\\/g, "/") : cachePath; + const args = IS_WINDOWS ? ["-xz", "--force-local"] : ["-xz"]; + args.push(...["-f", tarArchivePath, "-C", tarCachePath]); + + expect(execMock).toHaveBeenCalledTimes(1); + expect(execMock).toHaveBeenCalledWith(`"tar"`, args); + + expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); + expect(setCacheHitOutputMock).toHaveBeenCalledWith(false); + + expect(infoMock).toHaveBeenCalledWith( + `Cache restored from key: ${restoreKey}` + ); + expect(warningMock).toHaveBeenCalledTimes(0); + expect(failedMock).toHaveBeenCalledTimes(0); +}); diff --git a/jest.config.js b/jest.config.js index 42e7e56..095e90a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,3 +1,5 @@ +require('nock').disableNetConnect(); + module.exports = { clearMocks: true, moduleFileExtensions: ['js', 'ts'], diff --git a/package-lock.json b/package-lock.json index a3dc4ea..26a0903 100644 --- a/package-lock.json +++ b/package-lock.json @@ -517,6 +517,15 @@ "integrity": "sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA==", "dev": true }, + "@types/nock": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@types/nock/-/nock-11.1.0.tgz", + "integrity": "sha512-jI/ewavBQ7X5178262JQR0ewicPAcJhXS/iFaNJl0VHLfyosZ/kwSrsa6VNQNSO8i9d8SqdRgOtZSOKJ/+iNMw==", + "dev": true, + "requires": { + "nock": "*" + } + }, "@types/node": { "version": "12.6.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.6.9.tgz", @@ -674,6 +683,12 @@ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -984,6 +999,20 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -995,6 +1024,12 @@ "supports-color": "^5.3.0" } }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", @@ -1234,6 +1269,15 @@ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -2261,6 +2305,12 @@ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -3675,6 +3725,37 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "nock": { + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/nock/-/nock-11.7.0.tgz", + "integrity": "sha512-7c1jhHew74C33OBeRYyQENT+YXQiejpwIrEjinh6dRurBae+Ei4QjeUaPlkptIF0ZacEiVCnw8dWaxqepkiihg==", + "dev": true, + "requires": { + "chai": "^4.1.2", + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.13", + "mkdirp": "^0.5.0", + "propagate": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -4017,6 +4098,12 @@ "pify": "^3.0.0" } }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -4090,6 +4177,12 @@ "sisteransi": "^1.0.0" } }, + "propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true + }, "psl": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.3.0.tgz", @@ -4965,6 +5058,12 @@ "prelude-ls": "~1.1.2" } }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "typed-rest-client": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.5.0.tgz", diff --git a/package.json b/package.json index a235bd7..568e958 100644 --- a/package.json +++ b/package.json @@ -31,11 +31,13 @@ }, "devDependencies": { "@types/jest": "^24.0.13", + "@types/nock": "^11.1.0", "@types/node": "^12.0.4", "@types/uuid": "^3.4.5", "@zeit/ncc": "^0.20.5", "jest": "^24.8.0", "jest-circus": "^24.7.1", + "nock": "^11.7.0", "prettier": "1.18.2", "ts-jest": "^24.0.2", "typescript": "^3.6.4" diff --git a/src/restore.ts b/src/restore.ts index 060c8d4..4080098 100644 --- a/src/restore.ts +++ b/src/restore.ts @@ -2,7 +2,6 @@ import * as core from "@actions/core"; import { exec } from "@actions/exec"; import * as io from "@actions/io"; -import * as fs from "fs"; import * as path from "path"; import * as cacheHttpClient from "./cacheHttpClient"; @@ -72,6 +71,9 @@ async function run() { // Download the cache from the cache entry await cacheHttpClient.downloadCache(cacheEntry, archivePath); + const archiveFileSize = utils.getArchiveFileSize(archivePath); + core.debug(`File Size: ${archiveFileSize}`); + io.mkdirP(cachePath); // http://man7.org/linux/man-pages/man1/tar.1.html @@ -89,9 +91,6 @@ async function run() { const tarPath = await io.which("tar", true); core.debug(`Tar Path: ${tarPath}`); - const archiveFileSize = fs.statSync(archivePath).size; - core.debug(`File Size: ${archiveFileSize}`); - await exec(`"${tarPath}"`, args); const isExactKeyMatch = utils.isExactKeyMatch( diff --git a/src/utils/actionUtils.ts b/src/utils/actionUtils.ts index d4d7638..9ad0072 100644 --- a/src/utils/actionUtils.ts +++ b/src/utils/actionUtils.ts @@ -1,5 +1,6 @@ import * as core from "@actions/core"; import * as io from "@actions/io"; +import * as fs from "fs"; import * as os from "os"; import * as path from "path"; import * as uuidV4 from "uuid/v4"; @@ -32,6 +33,10 @@ export async function createTempDirectory(): Promise { return dest; } +export function getArchiveFileSize(path: string): number { + return fs.statSync(path).size; +} + export function isExactKeyMatch( key: string, cacheResult?: ArtifactCacheEntry diff --git a/src/utils/testUtils.ts b/src/utils/testUtils.ts index 67121c7..87cfc4f 100644 --- a/src/utils/testUtils.ts +++ b/src/utils/testUtils.ts @@ -1,3 +1,6 @@ +import { Inputs } from "../constants"; + +// See: https://github.com/actions/toolkit/blob/master/packages/core/src/core.ts#L67 function getInputName(name: string): string { return `INPUT_${name.replace(/ /g, "_").toUpperCase()}`; } @@ -5,3 +8,22 @@ function getInputName(name: string): string { export function setInput(name: string, value: string) { process.env[getInputName(name)] = value; } + +interface CacheInput { + path: string; + key: string; + restoreKeys?: string[]; +} + +export function setInputs(input: CacheInput) { + setInput(Inputs.Path, input.path); + setInput(Inputs.Key, input.key); + input.restoreKeys && + setInput(Inputs.RestoreKeys, input.restoreKeys.join("\n")); +} + +export function clearInputs() { + delete process.env[getInputName(Inputs.Path)]; + delete process.env[getInputName(Inputs.Key)]; + delete process.env[getInputName(Inputs.RestoreKeys)]; +}