Enhancement: Allow usage when GITHUB_REF or ACTIONS_CACHE_REF are defined

This commit is contained in:
Andreas Möller 2020-05-16 23:05:56 +02:00
parent 16a133d9a7
commit 77fd223211
No known key found for this signature in database
GPG key ID: 9FB20A0BAF60E11F
7 changed files with 792 additions and 638 deletions

View file

@ -4,7 +4,7 @@ import { promises as fs } from "fs";
import * as os from "os"; import * as os from "os";
import * as path from "path"; import * as path from "path";
import { Events, Outputs, RefKey, State } from "../src/constants"; import { Events, Outputs, RefKeys, State } from "../src/constants";
import { ArtifactCacheEntry } from "../src/contracts"; import { ArtifactCacheEntry } from "../src/contracts";
import * as actionUtils from "../src/utils/actionUtils"; import * as actionUtils from "../src/utils/actionUtils";
@ -19,7 +19,8 @@ function getTempDir(): string {
afterEach(() => { afterEach(() => {
delete process.env[Events.Key]; delete process.env[Events.Key];
delete process.env[RefKey];
RefKeys.forEach(refKey => delete process.env[refKey]);
}); });
afterAll(async () => { afterAll(async () => {
@ -326,16 +327,23 @@ test("resolvePaths exclusion pattern returns not found", async () => {
} }
}); });
test("isValidEvent returns true for event that has a ref", () => { const refKeySet = RefKeys.map(refKey => {
const event = Events.Push; return [refKey];
process.env[Events.Key] = event;
process.env[RefKey] = "ref/heads/feature";
const isValidEvent = actionUtils.isValidEvent();
expect(isValidEvent).toBe(true);
}); });
test.each(refKeySet)(
"isValidEvent returns true for event that has a ref",
refKey => {
const event = Events.Push;
process.env[Events.Key] = event;
process.env[refKey] = "ref/heads/feature";
const isValidEvent = actionUtils.isValidEvent();
expect(isValidEvent).toBe(true);
}
);
test("unlinkFile unlinks file", async () => { test("unlinkFile unlinks file", async () => {
const testDirectory = await fs.mkdtemp("unlinkFileTest"); const testDirectory = await fs.mkdtemp("unlinkFileTest");
const testFile = path.join(testDirectory, "test.txt"); const testFile = path.join(testDirectory, "test.txt");

View file

@ -7,7 +7,7 @@ import {
CompressionMethod, CompressionMethod,
Events, Events,
Inputs, Inputs,
RefKey RefKeys
} from "../src/constants"; } from "../src/constants";
import { ArtifactCacheEntry } from "../src/contracts"; import { ArtifactCacheEntry } from "../src/contracts";
import run from "../src/restore"; import run from "../src/restore";
@ -40,13 +40,17 @@ beforeAll(() => {
beforeEach(() => { beforeEach(() => {
process.env[Events.Key] = Events.Push; process.env[Events.Key] = Events.Push;
process.env[RefKey] = "refs/heads/feature-branch";
}); });
afterEach(() => { afterEach(() => {
testUtils.clearInputs(); testUtils.clearInputs();
delete process.env[Events.Key]; delete process.env[Events.Key];
delete process.env[RefKey];
RefKeys.forEach(refKey => delete process.env[refKey]);
});
const refKeySet = RefKeys.map(refKey => {
return [refKey, `refs/heads/feature/${refKey.toLowerCase()}`];
}); });
test("restore with invalid event outputs warning", async () => { test("restore with invalid event outputs warning", async () => {
@ -54,7 +58,6 @@ test("restore with invalid event outputs warning", async () => {
const failedMock = jest.spyOn(core, "setFailed"); const failedMock = jest.spyOn(core, "setFailed");
const invalidEvent = "commit_comment"; const invalidEvent = "commit_comment";
process.env[Events.Key] = invalidEvent; process.env[Events.Key] = invalidEvent;
delete process.env[RefKey];
await run(); await run();
expect(logWarningMock).toHaveBeenCalledWith( expect(logWarningMock).toHaveBeenCalledWith(
`Event Validation Error: The event type ${invalidEvent} is not supported because it's not tied to a branch or tag ref.` `Event Validation Error: The event type ${invalidEvent} is not supported because it's not tied to a branch or tag ref.`
@ -62,16 +65,23 @@ test("restore with invalid event outputs warning", async () => {
expect(failedMock).toHaveBeenCalledTimes(0); expect(failedMock).toHaveBeenCalledTimes(0);
}); });
test("restore with no path should fail", async () => { test.each(refKeySet)(
const failedMock = jest.spyOn(core, "setFailed"); "restore with no path should fail",
await run(); async (refKey, ref) => {
// this input isn't necessary for restore b/c tarball contains entries relative to workspace process.env[refKey] = ref;
expect(failedMock).not.toHaveBeenCalledWith(
"Input required and not supplied: path" const failedMock = jest.spyOn(core, "setFailed");
); await run();
}); // 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.each(refKeySet)("restore with no key", async (refKey, ref) => {
process.env[refKey] = ref;
test("restore with no key", async () => {
testUtils.setInput(Inputs.Path, "node_modules"); testUtils.setInput(Inputs.Path, "node_modules");
const failedMock = jest.spyOn(core, "setFailed"); const failedMock = jest.spyOn(core, "setFailed");
await run(); await run();
@ -80,48 +90,65 @@ test("restore with no key", async () => {
); );
}); });
test("restore with too many keys should fail", async () => { test.each(refKeySet)(
const key = "node-test"; "restore with too many keys should fail",
const restoreKeys = [...Array(20).keys()].map(x => x.toString()); async (refKey, ref) => {
testUtils.setInputs({ process.env[refKey] = ref;
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 = "node-test";
const key = "foo".repeat(512); // Over the 512 character limit const restoreKeys = [...Array(20).keys()].map(x => x.toString());
testUtils.setInputs({ testUtils.setInputs({
path: "node_modules", path: "node_modules",
key key,
}); restoreKeys
const failedMock = jest.spyOn(core, "setFailed"); });
await run(); const failedMock = jest.spyOn(core, "setFailed");
expect(failedMock).toHaveBeenCalledWith( await run();
`Key Validation Error: ${key} cannot be larger than 512 characters.` expect(failedMock).toHaveBeenCalledWith(
); `Key Validation Error: Keys are limited to a maximum of 10.`
}); );
}
);
test("restore with invalid key should fail", async () => { test.each(refKeySet)(
const key = "comma,comma"; "restore with large key should fail",
testUtils.setInputs({ async (refKey, ref) => {
path: "node_modules", process.env[refKey] = ref;
key
}); const key = "foo".repeat(512); // Over the 512 character limit
const failedMock = jest.spyOn(core, "setFailed"); testUtils.setInputs({
await run(); path: "node_modules",
expect(failedMock).toHaveBeenCalledWith( key
`Key Validation Error: ${key} cannot contain commas.` });
); const failedMock = jest.spyOn(core, "setFailed");
}); await run();
expect(failedMock).toHaveBeenCalledWith(
`Key Validation Error: ${key} cannot be larger than 512 characters.`
);
}
);
test.each(refKeySet)(
"restore with invalid key should fail",
async (refKey, ref) => {
process.env[refKey] = ref;
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.each(refKeySet)("restore with no cache found", async (refKey, ref) => {
process.env[refKey] = ref;
test("restore with no cache found", async () => {
const key = "node-test"; const key = "node-test";
testUtils.setInputs({ testUtils.setInputs({
path: "node_modules", path: "node_modules",
@ -147,287 +174,330 @@ test("restore with no cache found", async () => {
); );
}); });
test("restore with server error should fail", async () => { test.each(refKeySet)(
const key = "node-test"; "restore with server error should fail",
testUtils.setInputs({ async (refKey, ref) => {
path: "node_modules", process.env[refKey] = ref;
key
});
const logWarningMock = jest.spyOn(actionUtils, "logWarning"); const key = "node-test";
const failedMock = jest.spyOn(core, "setFailed"); testUtils.setInputs({
const stateMock = jest.spyOn(core, "saveState"); path: "node_modules",
key
});
const clientMock = jest.spyOn(cacheHttpClient, "getCacheEntry"); const logWarningMock = jest.spyOn(actionUtils, "logWarning");
clientMock.mockImplementation(() => { const failedMock = jest.spyOn(core, "setFailed");
throw new Error("HTTP Error Occurred"); const stateMock = jest.spyOn(core, "saveState");
});
const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput"); const clientMock = jest.spyOn(cacheHttpClient, "getCacheEntry");
clientMock.mockImplementation(() => {
throw new Error("HTTP Error Occurred");
});
await run(); const setCacheHitOutputMock = jest.spyOn(
actionUtils,
"setCacheHitOutput"
);
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); await run();
expect(logWarningMock).toHaveBeenCalledTimes(1); expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
expect(logWarningMock).toHaveBeenCalledWith("HTTP Error Occurred");
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); expect(logWarningMock).toHaveBeenCalledTimes(1);
expect(setCacheHitOutputMock).toHaveBeenCalledWith(false); expect(logWarningMock).toHaveBeenCalledWith("HTTP Error Occurred");
expect(failedMock).toHaveBeenCalledTimes(0); expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
}); expect(setCacheHitOutputMock).toHaveBeenCalledWith(false);
test("restore with restore keys and no cache found", async () => { expect(failedMock).toHaveBeenCalledTimes(0);
const key = "node-test"; }
const restoreKey = "node-"; );
testUtils.setInputs({
path: "node_modules",
key,
restoreKeys: [restoreKey]
});
const infoMock = jest.spyOn(core, "info"); test.each(refKeySet)(
const failedMock = jest.spyOn(core, "setFailed"); "restore with restore keys and no cache found",
const stateMock = jest.spyOn(core, "saveState"); async (refKey, ref) => {
process.env[refKey] = ref;
const clientMock = jest.spyOn(cacheHttpClient, "getCacheEntry"); const key = "node-test";
clientMock.mockImplementation(() => { const restoreKey = "node-";
return Promise.resolve(null); testUtils.setInputs({
}); path: "node_modules",
key,
restoreKeys: [restoreKey]
});
await run(); const infoMock = jest.spyOn(core, "info");
const failedMock = jest.spyOn(core, "setFailed");
const stateMock = jest.spyOn(core, "saveState");
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); const clientMock = jest.spyOn(cacheHttpClient, "getCacheEntry");
expect(failedMock).toHaveBeenCalledTimes(0); clientMock.mockImplementation(() => {
return Promise.resolve(null);
});
expect(infoMock).toHaveBeenCalledWith( await run();
`Cache not found for input keys: ${key}, ${restoreKey}`
);
});
test("restore with gzip compressed cache found", async () => { expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
const key = "node-test"; expect(failedMock).toHaveBeenCalledTimes(0);
testUtils.setInputs({
path: "node_modules",
key
});
const infoMock = jest.spyOn(core, "info"); expect(infoMock).toHaveBeenCalledWith(
const failedMock = jest.spyOn(core, "setFailed"); `Cache not found for input keys: ${key}, ${restoreKey}`
const stateMock = jest.spyOn(core, "saveState"); );
}
);
const cacheEntry: ArtifactCacheEntry = { test.each(refKeySet)(
cacheKey: key, "restore with gzip compressed cache found",
scope: "refs/heads/master", async (refKey, ref) => {
archiveLocation: "www.actionscache.test/download" process.env[refKey] = ref;
};
const getCacheMock = jest.spyOn(cacheHttpClient, "getCacheEntry");
getCacheMock.mockImplementation(() => {
return Promise.resolve(cacheEntry);
});
const tempPath = "/foo/bar";
const createTempDirectoryMock = jest.spyOn( const key = "node-test";
actionUtils, testUtils.setInputs({
"createTempDirectory" path: "node_modules",
); key
createTempDirectoryMock.mockImplementation(() => { });
return Promise.resolve(tempPath);
});
const archivePath = path.join(tempPath, CacheFilename.Gzip); const infoMock = jest.spyOn(core, "info");
const setCacheStateMock = jest.spyOn(actionUtils, "setCacheState"); const failedMock = jest.spyOn(core, "setFailed");
const downloadCacheMock = jest.spyOn(cacheHttpClient, "downloadCache"); const stateMock = jest.spyOn(core, "saveState");
const fileSize = 142; const cacheEntry: ArtifactCacheEntry = {
const getArchiveFileSizeMock = jest cacheKey: key,
.spyOn(actionUtils, "getArchiveFileSize") scope: "refs/heads/master",
.mockReturnValue(fileSize); archiveLocation: "www.actionscache.test/download"
};
const getCacheMock = jest.spyOn(cacheHttpClient, "getCacheEntry");
getCacheMock.mockImplementation(() => {
return Promise.resolve(cacheEntry);
});
const tempPath = "/foo/bar";
const extractTarMock = jest.spyOn(tar, "extractTar"); const createTempDirectoryMock = jest.spyOn(
const unlinkFileMock = jest.spyOn(actionUtils, "unlinkFile"); actionUtils,
const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput"); "createTempDirectory"
);
createTempDirectoryMock.mockImplementation(() => {
return Promise.resolve(tempPath);
});
const compression = CompressionMethod.Gzip; const archivePath = path.join(tempPath, CacheFilename.Gzip);
const getCompressionMock = jest const setCacheStateMock = jest.spyOn(actionUtils, "setCacheState");
.spyOn(actionUtils, "getCompressionMethod") const downloadCacheMock = jest.spyOn(cacheHttpClient, "downloadCache");
.mockReturnValue(Promise.resolve(compression));
await run(); const fileSize = 142;
const getArchiveFileSizeMock = jest
.spyOn(actionUtils, "getArchiveFileSize")
.mockReturnValue(fileSize);
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); const extractTarMock = jest.spyOn(tar, "extractTar");
expect(getCacheMock).toHaveBeenCalledWith([key], { const unlinkFileMock = jest.spyOn(actionUtils, "unlinkFile");
compressionMethod: compression const setCacheHitOutputMock = jest.spyOn(
}); actionUtils,
expect(setCacheStateMock).toHaveBeenCalledWith(cacheEntry); "setCacheHitOutput"
expect(createTempDirectoryMock).toHaveBeenCalledTimes(1); );
expect(downloadCacheMock).toHaveBeenCalledWith(
cacheEntry.archiveLocation,
archivePath
);
expect(getArchiveFileSizeMock).toHaveBeenCalledWith(archivePath);
expect(extractTarMock).toHaveBeenCalledTimes(1); const compression = CompressionMethod.Gzip;
expect(extractTarMock).toHaveBeenCalledWith(archivePath, compression); const getCompressionMock = jest
.spyOn(actionUtils, "getCompressionMethod")
.mockReturnValue(Promise.resolve(compression));
expect(unlinkFileMock).toHaveBeenCalledTimes(1); await run();
expect(unlinkFileMock).toHaveBeenCalledWith(archivePath);
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
expect(setCacheHitOutputMock).toHaveBeenCalledWith(true); expect(getCacheMock).toHaveBeenCalledWith([key], {
compressionMethod: compression
});
expect(setCacheStateMock).toHaveBeenCalledWith(cacheEntry);
expect(createTempDirectoryMock).toHaveBeenCalledTimes(1);
expect(downloadCacheMock).toHaveBeenCalledWith(
cacheEntry.archiveLocation,
archivePath
);
expect(getArchiveFileSizeMock).toHaveBeenCalledWith(archivePath);
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`); expect(extractTarMock).toHaveBeenCalledTimes(1);
expect(failedMock).toHaveBeenCalledTimes(0); expect(extractTarMock).toHaveBeenCalledWith(archivePath, compression);
expect(getCompressionMock).toHaveBeenCalledTimes(1);
});
test("restore with a pull request event and zstd compressed cache found", async () => { expect(unlinkFileMock).toHaveBeenCalledTimes(1);
const key = "node-test"; expect(unlinkFileMock).toHaveBeenCalledWith(archivePath);
testUtils.setInputs({
path: "node_modules",
key
});
process.env[Events.Key] = Events.PullRequest; expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
expect(setCacheHitOutputMock).toHaveBeenCalledWith(true);
const infoMock = jest.spyOn(core, "info"); expect(infoMock).toHaveBeenCalledWith(
const failedMock = jest.spyOn(core, "setFailed"); `Cache restored from key: ${key}`
const stateMock = jest.spyOn(core, "saveState"); );
expect(failedMock).toHaveBeenCalledTimes(0);
expect(getCompressionMock).toHaveBeenCalledTimes(1);
}
);
const cacheEntry: ArtifactCacheEntry = { test.each(refKeySet)(
cacheKey: key, "restore with a pull request event and zstd compressed cache found",
scope: "refs/heads/master", async (refKey, ref) => {
archiveLocation: "www.actionscache.test/download" process.env[refKey] = ref;
};
const getCacheMock = jest.spyOn(cacheHttpClient, "getCacheEntry");
getCacheMock.mockImplementation(() => {
return Promise.resolve(cacheEntry);
});
const tempPath = "/foo/bar";
const createTempDirectoryMock = jest.spyOn( const key = "node-test";
actionUtils, testUtils.setInputs({
"createTempDirectory" path: "node_modules",
); key
createTempDirectoryMock.mockImplementation(() => { });
return Promise.resolve(tempPath);
});
const archivePath = path.join(tempPath, CacheFilename.Zstd); process.env[Events.Key] = Events.PullRequest;
const setCacheStateMock = jest.spyOn(actionUtils, "setCacheState");
const downloadCacheMock = jest.spyOn(cacheHttpClient, "downloadCache");
const fileSize = 62915000; const infoMock = jest.spyOn(core, "info");
const getArchiveFileSizeMock = jest const failedMock = jest.spyOn(core, "setFailed");
.spyOn(actionUtils, "getArchiveFileSize") const stateMock = jest.spyOn(core, "saveState");
.mockReturnValue(fileSize);
const extractTarMock = jest.spyOn(tar, "extractTar"); const cacheEntry: ArtifactCacheEntry = {
const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput"); cacheKey: key,
const compression = CompressionMethod.Zstd; scope: "refs/heads/master",
const getCompressionMock = jest archiveLocation: "www.actionscache.test/download"
.spyOn(actionUtils, "getCompressionMethod") };
.mockReturnValue(Promise.resolve(compression)); const getCacheMock = jest.spyOn(cacheHttpClient, "getCacheEntry");
getCacheMock.mockImplementation(() => {
return Promise.resolve(cacheEntry);
});
const tempPath = "/foo/bar";
await run(); const createTempDirectoryMock = jest.spyOn(
actionUtils,
"createTempDirectory"
);
createTempDirectoryMock.mockImplementation(() => {
return Promise.resolve(tempPath);
});
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); const archivePath = path.join(tempPath, CacheFilename.Zstd);
expect(getCacheMock).toHaveBeenCalledWith([key], { const setCacheStateMock = jest.spyOn(actionUtils, "setCacheState");
compressionMethod: compression const downloadCacheMock = jest.spyOn(cacheHttpClient, "downloadCache");
});
expect(setCacheStateMock).toHaveBeenCalledWith(cacheEntry);
expect(createTempDirectoryMock).toHaveBeenCalledTimes(1);
expect(downloadCacheMock).toHaveBeenCalledWith(
cacheEntry.archiveLocation,
archivePath
);
expect(getArchiveFileSizeMock).toHaveBeenCalledWith(archivePath);
expect(infoMock).toHaveBeenCalledWith(`Cache Size: ~60 MB (62915000 B)`);
expect(extractTarMock).toHaveBeenCalledTimes(1); const fileSize = 62915000;
expect(extractTarMock).toHaveBeenCalledWith(archivePath, compression); const getArchiveFileSizeMock = jest
.spyOn(actionUtils, "getArchiveFileSize")
.mockReturnValue(fileSize);
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); const extractTarMock = jest.spyOn(tar, "extractTar");
expect(setCacheHitOutputMock).toHaveBeenCalledWith(true); const setCacheHitOutputMock = jest.spyOn(
actionUtils,
"setCacheHitOutput"
);
const compression = CompressionMethod.Zstd;
const getCompressionMock = jest
.spyOn(actionUtils, "getCompressionMethod")
.mockReturnValue(Promise.resolve(compression));
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`); await run();
expect(failedMock).toHaveBeenCalledTimes(0);
expect(getCompressionMock).toHaveBeenCalledTimes(1);
});
test("restore with cache found for restore key", async () => { expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
const key = "node-test"; expect(getCacheMock).toHaveBeenCalledWith([key], {
const restoreKey = "node-"; compressionMethod: compression
testUtils.setInputs({ });
path: "node_modules", expect(setCacheStateMock).toHaveBeenCalledWith(cacheEntry);
key, expect(createTempDirectoryMock).toHaveBeenCalledTimes(1);
restoreKeys: [restoreKey] expect(downloadCacheMock).toHaveBeenCalledWith(
}); cacheEntry.archiveLocation,
archivePath
);
expect(getArchiveFileSizeMock).toHaveBeenCalledWith(archivePath);
expect(infoMock).toHaveBeenCalledWith(
`Cache Size: ~60 MB (62915000 B)`
);
const infoMock = jest.spyOn(core, "info"); expect(extractTarMock).toHaveBeenCalledTimes(1);
const failedMock = jest.spyOn(core, "setFailed"); expect(extractTarMock).toHaveBeenCalledWith(archivePath, compression);
const stateMock = jest.spyOn(core, "saveState");
const cacheEntry: ArtifactCacheEntry = { expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
cacheKey: restoreKey, expect(setCacheHitOutputMock).toHaveBeenCalledWith(true);
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( expect(infoMock).toHaveBeenCalledWith(
actionUtils, `Cache restored from key: ${key}`
"createTempDirectory" );
); expect(failedMock).toHaveBeenCalledTimes(0);
createTempDirectoryMock.mockImplementation(() => { expect(getCompressionMock).toHaveBeenCalledTimes(1);
return Promise.resolve(tempPath); }
}); );
const archivePath = path.join(tempPath, CacheFilename.Zstd); test.each(refKeySet)(
const setCacheStateMock = jest.spyOn(actionUtils, "setCacheState"); "restore with cache found for restore key",
const downloadCacheMock = jest.spyOn(cacheHttpClient, "downloadCache"); async (refKey, ref) => {
process.env[refKey] = ref;
const fileSize = 142; const key = "node-test";
const getArchiveFileSizeMock = jest const restoreKey = "node-";
.spyOn(actionUtils, "getArchiveFileSize") testUtils.setInputs({
.mockReturnValue(fileSize); path: "node_modules",
key,
restoreKeys: [restoreKey]
});
const extractTarMock = jest.spyOn(tar, "extractTar"); const infoMock = jest.spyOn(core, "info");
const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput"); const failedMock = jest.spyOn(core, "setFailed");
const compression = CompressionMethod.Zstd; const stateMock = jest.spyOn(core, "saveState");
const getCompressionMock = jest
.spyOn(actionUtils, "getCompressionMethod")
.mockReturnValue(Promise.resolve(compression));
await run(); 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";
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); const createTempDirectoryMock = jest.spyOn(
expect(getCacheMock).toHaveBeenCalledWith([key, restoreKey], { actionUtils,
compressionMethod: compression "createTempDirectory"
}); );
expect(setCacheStateMock).toHaveBeenCalledWith(cacheEntry); createTempDirectoryMock.mockImplementation(() => {
expect(createTempDirectoryMock).toHaveBeenCalledTimes(1); return Promise.resolve(tempPath);
expect(downloadCacheMock).toHaveBeenCalledWith( });
cacheEntry.archiveLocation,
archivePath
);
expect(getArchiveFileSizeMock).toHaveBeenCalledWith(archivePath);
expect(infoMock).toHaveBeenCalledWith(`Cache Size: ~0 MB (142 B)`);
expect(extractTarMock).toHaveBeenCalledTimes(1); const archivePath = path.join(tempPath, CacheFilename.Zstd);
expect(extractTarMock).toHaveBeenCalledWith(archivePath, compression); const setCacheStateMock = jest.spyOn(actionUtils, "setCacheState");
const downloadCacheMock = jest.spyOn(cacheHttpClient, "downloadCache");
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); const fileSize = 142;
expect(setCacheHitOutputMock).toHaveBeenCalledWith(false); const getArchiveFileSizeMock = jest
.spyOn(actionUtils, "getArchiveFileSize")
.mockReturnValue(fileSize);
expect(infoMock).toHaveBeenCalledWith( const extractTarMock = jest.spyOn(tar, "extractTar");
`Cache restored from key: ${restoreKey}` const setCacheHitOutputMock = jest.spyOn(
); actionUtils,
expect(failedMock).toHaveBeenCalledTimes(0); "setCacheHitOutput"
expect(getCompressionMock).toHaveBeenCalledTimes(1); );
}); const compression = CompressionMethod.Zstd;
const getCompressionMock = jest
.spyOn(actionUtils, "getCompressionMethod")
.mockReturnValue(Promise.resolve(compression));
await run();
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
expect(getCacheMock).toHaveBeenCalledWith([key, restoreKey], {
compressionMethod: compression
});
expect(setCacheStateMock).toHaveBeenCalledWith(cacheEntry);
expect(createTempDirectoryMock).toHaveBeenCalledTimes(1);
expect(downloadCacheMock).toHaveBeenCalledWith(
cacheEntry.archiveLocation,
archivePath
);
expect(getArchiveFileSizeMock).toHaveBeenCalledWith(archivePath);
expect(infoMock).toHaveBeenCalledWith(`Cache Size: ~0 MB (142 B)`);
expect(extractTarMock).toHaveBeenCalledTimes(1);
expect(extractTarMock).toHaveBeenCalledWith(archivePath, compression);
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
expect(setCacheHitOutputMock).toHaveBeenCalledWith(false);
expect(infoMock).toHaveBeenCalledWith(
`Cache restored from key: ${restoreKey}`
);
expect(failedMock).toHaveBeenCalledTimes(0);
expect(getCompressionMock).toHaveBeenCalledTimes(1);
}
);

View file

@ -7,7 +7,7 @@ import {
CompressionMethod, CompressionMethod,
Events, Events,
Inputs, Inputs,
RefKey RefKeys
} from "../src/constants"; } from "../src/constants";
import { ArtifactCacheEntry } from "../src/contracts"; import { ArtifactCacheEntry } from "../src/contracts";
import run from "../src/save"; import run from "../src/save";
@ -60,368 +60,418 @@ beforeAll(() => {
beforeEach(() => { beforeEach(() => {
process.env[Events.Key] = Events.Push; process.env[Events.Key] = Events.Push;
process.env[RefKey] = "refs/heads/feature-branch";
}); });
afterEach(() => { afterEach(() => {
testUtils.clearInputs(); testUtils.clearInputs();
delete process.env[Events.Key]; delete process.env[Events.Key];
delete process.env[RefKey];
RefKeys.forEach(refKey => delete process.env[refKey]);
}); });
test("save with invalid event outputs warning", async () => { const refKeySet = RefKeys.map(refKey => {
const logWarningMock = jest.spyOn(actionUtils, "logWarning"); return [refKey, `refs/heads/feature/${refKey.toLowerCase()}`];
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 () => { test.each(refKeySet)(
const logWarningMock = jest.spyOn(actionUtils, "logWarning"); "save with invalid event outputs warning",
const failedMock = jest.spyOn(core, "setFailed"); async refKey => {
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);
}
);
const cacheEntry: ArtifactCacheEntry = { test.each(refKeySet)(
cacheKey: "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43", "save with no primary key in state outputs warning",
scope: "refs/heads/master", async (refKey, ref) => {
creationTime: "2019-11-13T19:18:02+00:00", process.env[refKey] = ref;
archiveLocation: "www.actionscache.test/download"
};
jest.spyOn(core, "getState") const logWarningMock = jest.spyOn(actionUtils, "logWarning");
// Cache Entry State const failedMock = jest.spyOn(core, "setFailed");
.mockImplementationOnce(() => {
return JSON.stringify(cacheEntry); const cacheEntry: ArtifactCacheEntry = {
}) cacheKey: "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43",
// Cache Key State scope: "refs/heads/master",
.mockImplementationOnce(() => { creationTime: "2019-11-13T19:18:02+00:00",
return ""; 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(logWarningMock).toHaveBeenCalledWith(
`Error retrieving key from state.`
);
expect(logWarningMock).toHaveBeenCalledTimes(1);
expect(failedMock).toHaveBeenCalledTimes(0);
}
);
test.each(refKeySet)(
"save with exact match returns early",
async (refKey, ref) => {
process.env[refKey] = ref;
const infoMock = jest.spyOn(core, "info");
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 createTarMock = jest.spyOn(tar, "createTar");
await run();
expect(infoMock).toHaveBeenCalledWith(
`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`
);
expect(createTarMock).toHaveBeenCalledTimes(0);
expect(failedMock).toHaveBeenCalledTimes(0);
}
);
test.each(refKeySet)(
"save with missing input outputs warning",
async (refKey, ref) => {
process.env[refKey] = ref;
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
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(logWarningMock).toHaveBeenCalledWith(
"Input required and not supplied: path"
);
expect(logWarningMock).toHaveBeenCalledTimes(1);
expect(failedMock).toHaveBeenCalledTimes(0);
}
);
test.each(refKeySet)(
"save with large cache outputs warning",
async (refKey, ref) => {
process.env[refKey] = ref;
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
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 cachePaths = [path.resolve(inputPath)];
testUtils.setInput(Inputs.Path, inputPath);
const createTarMock = jest.spyOn(tar, "createTar");
const cacheSize = 6 * 1024 * 1024 * 1024; //~6GB, over the 5GB limit
jest.spyOn(actionUtils, "getArchiveFileSize").mockImplementationOnce(
() => {
return cacheSize;
}
);
const compression = CompressionMethod.Gzip;
const getCompressionMock = jest
.spyOn(actionUtils, "getCompressionMethod")
.mockReturnValue(Promise.resolve(compression));
await run();
const archiveFolder = "/foo/bar";
expect(createTarMock).toHaveBeenCalledTimes(1);
expect(createTarMock).toHaveBeenCalledWith(
archiveFolder,
cachePaths,
compression
);
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);
expect(getCompressionMock).toHaveBeenCalledTimes(1);
}
);
test.each(refKeySet)(
"save with reserve cache failure outputs warning",
async (refKey, ref) => {
process.env[refKey] = ref;
const infoMock = jest.spyOn(core, "info");
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
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";
testUtils.setInput(Inputs.Path, inputPath);
const reserveCacheMock = jest
.spyOn(cacheHttpClient, "reserveCache")
.mockImplementationOnce(() => {
return Promise.resolve(-1);
});
const createTarMock = jest.spyOn(tar, "createTar");
const saveCacheMock = jest.spyOn(cacheHttpClient, "saveCache");
const compression = CompressionMethod.Zstd;
const getCompressionMock = jest
.spyOn(actionUtils, "getCompressionMethod")
.mockReturnValue(Promise.resolve(compression));
await run();
expect(reserveCacheMock).toHaveBeenCalledTimes(1);
expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, {
compressionMethod: compression
}); });
await run(); expect(infoMock).toHaveBeenCalledWith(
`Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.`
);
expect(logWarningMock).toHaveBeenCalledWith( expect(createTarMock).toHaveBeenCalledTimes(0);
`Error retrieving key from state.` expect(saveCacheMock).toHaveBeenCalledTimes(0);
); expect(logWarningMock).toHaveBeenCalledTimes(0);
expect(logWarningMock).toHaveBeenCalledTimes(1); expect(failedMock).toHaveBeenCalledTimes(0);
expect(failedMock).toHaveBeenCalledTimes(0); expect(getCompressionMock).toHaveBeenCalledTimes(1);
}); }
);
test("save with exact match returns early", async () => { test.each(refKeySet)(
const infoMock = jest.spyOn(core, "info"); "save with server error outputs warning",
const failedMock = jest.spyOn(core, "setFailed"); async (refKey, ref) => {
process.env[refKey] = ref;
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; const logWarningMock = jest.spyOn(actionUtils, "logWarning");
const cacheEntry: ArtifactCacheEntry = { const failedMock = jest.spyOn(core, "setFailed");
cacheKey: primaryKey,
scope: "refs/heads/master",
creationTime: "2019-11-13T19:18:02+00:00",
archiveLocation: "www.actionscache.test/download"
};
jest.spyOn(core, "getState") const primaryKey =
// Cache Entry State "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
.mockImplementationOnce(() => { const cacheEntry: ArtifactCacheEntry = {
return JSON.stringify(cacheEntry); cacheKey: "Linux-node-",
}) scope: "refs/heads/master",
// Cache Key State creationTime: "2019-11-13T19:18:02+00:00",
.mockImplementationOnce(() => { archiveLocation: "www.actionscache.test/download"
return primaryKey; };
jest.spyOn(core, "getState")
// Cache Entry State
.mockImplementationOnce(() => {
return JSON.stringify(cacheEntry);
})
// Cache Key State
.mockImplementationOnce(() => {
return primaryKey;
});
const inputPath = "node_modules";
const cachePaths = [path.resolve(inputPath)];
testUtils.setInput(Inputs.Path, inputPath);
const cacheId = 4;
const reserveCacheMock = jest
.spyOn(cacheHttpClient, "reserveCache")
.mockImplementationOnce(() => {
return Promise.resolve(cacheId);
});
const createTarMock = jest.spyOn(tar, "createTar");
const saveCacheMock = jest
.spyOn(cacheHttpClient, "saveCache")
.mockImplementationOnce(() => {
throw new Error("HTTP Error Occurred");
});
const compression = CompressionMethod.Zstd;
const getCompressionMock = jest
.spyOn(actionUtils, "getCompressionMethod")
.mockReturnValue(Promise.resolve(compression));
await run();
expect(reserveCacheMock).toHaveBeenCalledTimes(1);
expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, {
compressionMethod: compression
}); });
const createTarMock = jest.spyOn(tar, "createTar"); const archiveFolder = "/foo/bar";
const archiveFile = path.join(archiveFolder, CacheFilename.Zstd);
await run(); expect(createTarMock).toHaveBeenCalledTimes(1);
expect(createTarMock).toHaveBeenCalledWith(
archiveFolder,
cachePaths,
compression
);
expect(infoMock).toHaveBeenCalledWith( expect(saveCacheMock).toHaveBeenCalledTimes(1);
`Cache hit occurred on the primary key ${primaryKey}, not saving cache.` expect(saveCacheMock).toHaveBeenCalledWith(cacheId, archiveFile);
);
expect(createTarMock).toHaveBeenCalledTimes(0); expect(logWarningMock).toHaveBeenCalledTimes(1);
expect(logWarningMock).toHaveBeenCalledWith("HTTP Error Occurred");
expect(failedMock).toHaveBeenCalledTimes(0); expect(failedMock).toHaveBeenCalledTimes(0);
}); expect(getCompressionMock).toHaveBeenCalledTimes(1);
}
);
test("save with missing input outputs warning", async () => { test.each(refKeySet)(
const logWarningMock = jest.spyOn(actionUtils, "logWarning"); "save with valid inputs uploads a cache",
const failedMock = jest.spyOn(core, "setFailed"); async (refKey, ref) => {
process.env[refKey] = ref;
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; const failedMock = jest.spyOn(core, "setFailed");
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") const primaryKey =
// Cache Entry State "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
.mockImplementationOnce(() => { const cacheEntry: ArtifactCacheEntry = {
return JSON.stringify(cacheEntry); cacheKey: "Linux-node-",
}) scope: "refs/heads/master",
// Cache Key State creationTime: "2019-11-13T19:18:02+00:00",
.mockImplementationOnce(() => { archiveLocation: "www.actionscache.test/download"
return primaryKey; };
jest.spyOn(core, "getState")
// Cache Entry State
.mockImplementationOnce(() => {
return JSON.stringify(cacheEntry);
})
// Cache Key State
.mockImplementationOnce(() => {
return primaryKey;
});
const inputPath = "node_modules";
const cachePaths = [path.resolve(inputPath)];
testUtils.setInput(Inputs.Path, inputPath);
const cacheId = 4;
const reserveCacheMock = jest
.spyOn(cacheHttpClient, "reserveCache")
.mockImplementationOnce(() => {
return Promise.resolve(cacheId);
});
const createTarMock = jest.spyOn(tar, "createTar");
const saveCacheMock = jest.spyOn(cacheHttpClient, "saveCache");
const compression = CompressionMethod.Zstd;
const getCompressionMock = jest
.spyOn(actionUtils, "getCompressionMethod")
.mockReturnValue(Promise.resolve(compression));
await run();
expect(reserveCacheMock).toHaveBeenCalledTimes(1);
expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, {
compressionMethod: compression
}); });
await run(); const archiveFolder = "/foo/bar";
const archiveFile = path.join(archiveFolder, CacheFilename.Zstd);
expect(logWarningMock).toHaveBeenCalledWith( expect(createTarMock).toHaveBeenCalledTimes(1);
"Input required and not supplied: path" expect(createTarMock).toHaveBeenCalledWith(
); archiveFolder,
expect(logWarningMock).toHaveBeenCalledTimes(1); cachePaths,
expect(failedMock).toHaveBeenCalledTimes(0); compression
}); );
test("save with large cache outputs warning", async () => { expect(saveCacheMock).toHaveBeenCalledTimes(1);
const logWarningMock = jest.spyOn(actionUtils, "logWarning"); expect(saveCacheMock).toHaveBeenCalledWith(cacheId, archiveFile);
const failedMock = jest.spyOn(core, "setFailed");
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; expect(failedMock).toHaveBeenCalledTimes(0);
const cacheEntry: ArtifactCacheEntry = { expect(getCompressionMock).toHaveBeenCalledTimes(1);
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 cachePaths = [path.resolve(inputPath)];
testUtils.setInput(Inputs.Path, inputPath);
const createTarMock = jest.spyOn(tar, "createTar");
const cacheSize = 6 * 1024 * 1024 * 1024; //~6GB, over the 5GB limit
jest.spyOn(actionUtils, "getArchiveFileSize").mockImplementationOnce(() => {
return cacheSize;
});
const compression = CompressionMethod.Gzip;
const getCompressionMock = jest
.spyOn(actionUtils, "getCompressionMethod")
.mockReturnValue(Promise.resolve(compression));
await run();
const archiveFolder = "/foo/bar";
expect(createTarMock).toHaveBeenCalledTimes(1);
expect(createTarMock).toHaveBeenCalledWith(
archiveFolder,
cachePaths,
compression
);
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);
expect(getCompressionMock).toHaveBeenCalledTimes(1);
});
test("save with reserve cache failure outputs warning", async () => {
const infoMock = jest.spyOn(core, "info");
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
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";
testUtils.setInput(Inputs.Path, inputPath);
const reserveCacheMock = jest
.spyOn(cacheHttpClient, "reserveCache")
.mockImplementationOnce(() => {
return Promise.resolve(-1);
});
const createTarMock = jest.spyOn(tar, "createTar");
const saveCacheMock = jest.spyOn(cacheHttpClient, "saveCache");
const compression = CompressionMethod.Zstd;
const getCompressionMock = jest
.spyOn(actionUtils, "getCompressionMethod")
.mockReturnValue(Promise.resolve(compression));
await run();
expect(reserveCacheMock).toHaveBeenCalledTimes(1);
expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, {
compressionMethod: compression
});
expect(infoMock).toHaveBeenCalledWith(
`Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.`
);
expect(createTarMock).toHaveBeenCalledTimes(0);
expect(saveCacheMock).toHaveBeenCalledTimes(0);
expect(logWarningMock).toHaveBeenCalledTimes(0);
expect(failedMock).toHaveBeenCalledTimes(0);
expect(getCompressionMock).toHaveBeenCalledTimes(1);
});
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 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 cachePaths = [path.resolve(inputPath)];
testUtils.setInput(Inputs.Path, inputPath);
const cacheId = 4;
const reserveCacheMock = jest
.spyOn(cacheHttpClient, "reserveCache")
.mockImplementationOnce(() => {
return Promise.resolve(cacheId);
});
const createTarMock = jest.spyOn(tar, "createTar");
const saveCacheMock = jest
.spyOn(cacheHttpClient, "saveCache")
.mockImplementationOnce(() => {
throw new Error("HTTP Error Occurred");
});
const compression = CompressionMethod.Zstd;
const getCompressionMock = jest
.spyOn(actionUtils, "getCompressionMethod")
.mockReturnValue(Promise.resolve(compression));
await run();
expect(reserveCacheMock).toHaveBeenCalledTimes(1);
expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, {
compressionMethod: compression
});
const archiveFolder = "/foo/bar";
const archiveFile = path.join(archiveFolder, CacheFilename.Zstd);
expect(createTarMock).toHaveBeenCalledTimes(1);
expect(createTarMock).toHaveBeenCalledWith(
archiveFolder,
cachePaths,
compression
);
expect(saveCacheMock).toHaveBeenCalledTimes(1);
expect(saveCacheMock).toHaveBeenCalledWith(cacheId, archiveFile);
expect(logWarningMock).toHaveBeenCalledTimes(1);
expect(logWarningMock).toHaveBeenCalledWith("HTTP Error Occurred");
expect(failedMock).toHaveBeenCalledTimes(0);
expect(getCompressionMock).toHaveBeenCalledTimes(1);
});
test("save with valid inputs uploads a cache", async () => {
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 cachePaths = [path.resolve(inputPath)];
testUtils.setInput(Inputs.Path, inputPath);
const cacheId = 4;
const reserveCacheMock = jest
.spyOn(cacheHttpClient, "reserveCache")
.mockImplementationOnce(() => {
return Promise.resolve(cacheId);
});
const createTarMock = jest.spyOn(tar, "createTar");
const saveCacheMock = jest.spyOn(cacheHttpClient, "saveCache");
const compression = CompressionMethod.Zstd;
const getCompressionMock = jest
.spyOn(actionUtils, "getCompressionMethod")
.mockReturnValue(Promise.resolve(compression));
await run();
expect(reserveCacheMock).toHaveBeenCalledTimes(1);
expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, {
compressionMethod: compression
});
const archiveFolder = "/foo/bar";
const archiveFile = path.join(archiveFolder, CacheFilename.Zstd);
expect(createTarMock).toHaveBeenCalledTimes(1);
expect(createTarMock).toHaveBeenCalledWith(
archiveFolder,
cachePaths,
compression
);
expect(saveCacheMock).toHaveBeenCalledTimes(1);
expect(saveCacheMock).toHaveBeenCalledWith(cacheId, archiveFile);
expect(failedMock).toHaveBeenCalledTimes(0);
expect(getCompressionMock).toHaveBeenCalledTimes(1);
});

15
dist/restore/index.js vendored
View file

@ -3345,10 +3345,16 @@ function resolvePaths(patterns) {
}); });
} }
exports.resolvePaths = resolvePaths; exports.resolvePaths = resolvePaths;
// Cache token authorized for all events that are tied to a ref // Cache token authorized for events where a reference is defined
// See GitHub Context https://help.github.com/actions/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions#github-context // See GitHub Context https://help.github.com/actions/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions#github-context
function isValidEvent() { function isValidEvent() {
return constants_1.RefKey in process.env && Boolean(process.env[constants_1.RefKey]); for (let i = 0; i < constants_1.RefKeys.length; i++) {
let refKey = constants_1.RefKeys[i];
if (refKey in process.env) {
return Boolean(process.env[refKey]);
}
}
return false;
} }
exports.isValidEvent = isValidEvent; exports.isValidEvent = isValidEvent;
function unlinkFile(path) { function unlinkFile(path) {
@ -4607,7 +4613,10 @@ var CompressionMethod;
// over the socket during this period, the socket is destroyed and the download // over the socket during this period, the socket is destroyed and the download
// is aborted. // is aborted.
exports.SocketTimeout = 5000; exports.SocketTimeout = 5000;
exports.RefKey = "GITHUB_REF"; exports.RefKeys = [
"ACTIONS_CACHE_REF",
"GITHUB_REF",
];
/***/ }), /***/ }),

15
dist/save/index.js vendored
View file

@ -3345,10 +3345,16 @@ function resolvePaths(patterns) {
}); });
} }
exports.resolvePaths = resolvePaths; exports.resolvePaths = resolvePaths;
// Cache token authorized for all events that are tied to a ref // Cache token authorized for events where a reference is defined
// See GitHub Context https://help.github.com/actions/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions#github-context // See GitHub Context https://help.github.com/actions/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions#github-context
function isValidEvent() { function isValidEvent() {
return constants_1.RefKey in process.env && Boolean(process.env[constants_1.RefKey]); for (let i = 0; i < constants_1.RefKeys.length; i++) {
let refKey = constants_1.RefKeys[i];
if (refKey in process.env) {
return Boolean(process.env[refKey]);
}
}
return false;
} }
exports.isValidEvent = isValidEvent; exports.isValidEvent = isValidEvent;
function unlinkFile(path) { function unlinkFile(path) {
@ -4694,7 +4700,10 @@ var CompressionMethod;
// over the socket during this period, the socket is destroyed and the download // over the socket during this period, the socket is destroyed and the download
// is aborted. // is aborted.
exports.SocketTimeout = 5000; exports.SocketTimeout = 5000;
exports.RefKey = "GITHUB_REF"; exports.RefKeys = [
"ACTIONS_CACHE_REF",
"GITHUB_REF",
];
/***/ }), /***/ }),

View file

@ -34,4 +34,4 @@ export enum CompressionMethod {
// is aborted. // is aborted.
export const SocketTimeout = 5000; export const SocketTimeout = 5000;
export const RefKey = "GITHUB_REF"; export const RefKeys = ["ACTIONS_CACHE_REF", "GITHUB_REF"];

View file

@ -11,7 +11,7 @@ import {
CacheFilename, CacheFilename,
CompressionMethod, CompressionMethod,
Outputs, Outputs,
RefKey, RefKeys,
State State
} from "../constants"; } from "../constants";
import { ArtifactCacheEntry } from "../contracts"; import { ArtifactCacheEntry } from "../contracts";
@ -108,10 +108,18 @@ export async function resolvePaths(patterns: string[]): Promise<string[]> {
return paths; return paths;
} }
// Cache token authorized for all events that are tied to a ref // Cache token authorized for events where a reference is defined
// See GitHub Context https://help.github.com/actions/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions#github-context // See GitHub Context https://help.github.com/actions/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions#github-context
export function isValidEvent(): boolean { export function isValidEvent(): boolean {
return RefKey in process.env && Boolean(process.env[RefKey]); for (let i = 0; i < RefKeys.length; i++) {
let refKey = RefKeys[i];
if (refKey in process.env) {
return Boolean(process.env[refKey])
}
}
return false;
} }
export function unlinkFile(path: fs.PathLike): Promise<void> { export function unlinkFile(path: fs.PathLike): Promise<void> {