You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cache/__tests__/gcsCache.test.ts

167 lines
4.5 KiB
TypeScript

import * as cache from "@actions/cache";
import { Storage } from "@google-cloud/storage";
import * as actionUtils from "../src/utils/actionUtils";
import { restoreCache } from "../src/utils/gcsCache";
jest.mock("@actions/cache");
jest.mock("@google-cloud/storage");
jest.mock("../src/utils/actionUtils");
jest.mock("@actions/cache/lib/internal/cacheUtils", () => ({
getCompressionMethod: jest.fn().mockResolvedValue("zstd"),
getCacheFileName: jest.fn().mockReturnValue("cache.tzst"),
createTempDirectory: jest.fn().mockResolvedValue("/tmp/gcs-cache-test"),
getArchiveFileSizeInBytes: jest.fn().mockReturnValue(1024),
unlinkFile: jest.fn().mockResolvedValue(undefined),
resolvePaths: jest.fn().mockResolvedValue(["some/path"])
}));
jest.mock("@actions/cache/lib/internal/tar", () => ({
createTar: jest.fn(),
extractTar: jest.fn(),
listTar: jest.fn()
}));
const BUCKET = "test-bucket";
const PREFIX = "github-cache";
interface FakeObject {
name: string;
updated: string;
}
function mockStorage(objects: FakeObject[]): void {
const bucketApi = {
file: (path: string) => ({
exists: jest
.fn()
.mockResolvedValue([objects.some(o => o.name === path)]),
download: jest.fn().mockResolvedValue(undefined)
}),
getFiles: jest.fn(({ prefix }: { prefix: string }) => [
objects
.filter(o => o.name.startsWith(prefix))
.map(o => ({
name: o.name,
metadata: { updated: o.updated }
}))
])
};
(Storage as unknown as jest.Mock).mockImplementation(() => ({
bucket: () => bucketApi
}));
}
beforeEach(() => {
jest.mocked(actionUtils.isGCSAvailable).mockReturnValue(true);
jest.mocked(actionUtils.getGCSBucket).mockReturnValue(BUCKET);
jest.mocked(cache.restoreCache).mockResolvedValue(undefined);
});
afterEach(() => {
jest.clearAllMocks();
});
test("primary key exact match returns primary key", async () => {
mockStorage([
{
name: `${PREFIX}/nx-abc123.cache.tzst`,
updated: "2026-07-01T00:00:00Z"
}
]);
const result = await restoreCache(["some/path"], "nx-abc123", ["nx-"], {
lookupOnly: true
});
expect(result).toBe("nx-abc123");
});
test("restore key prefix-matches and returns newest entry's key", async () => {
mockStorage([
{
name: `${PREFIX}/nx-older00.cache.tzst`,
updated: "2026-07-01T00:00:00Z"
},
{
name: `${PREFIX}/nx-newer00.cache.tzst`,
updated: "2026-07-20T00:00:00Z"
}
]);
const result = await restoreCache(["some/path"], "nx-abc123", ["nx-"], {
lookupOnly: true
});
expect(result).toBe("nx-newer00");
});
test("restore key ignores objects with other compression suffix", async () => {
mockStorage([
{
name: `${PREFIX}/nx-gzipped0.cache.tgz`,
updated: "2026-07-20T00:00:00Z"
}
]);
const result = await restoreCache(["some/path"], "nx-abc123", ["nx-"], {
lookupOnly: true
});
expect(result).toBeUndefined();
expect(cache.restoreCache).toHaveBeenCalled();
});
test("restore keys are tried in order", async () => {
mockStorage([
{
name: `${PREFIX}/fallback-key.cache.tzst`,
updated: "2026-07-01T00:00:00Z"
},
{
name: `${PREFIX}/preferred-key.cache.tzst`,
updated: "2026-06-01T00:00:00Z"
}
]);
const result = await restoreCache(
["some/path"],
"nx-abc123",
["preferred-", "fallback-"],
{ lookupOnly: true }
);
expect(result).toBe("preferred-key");
});
test("no match falls back to GitHub cache", async () => {
mockStorage([]);
const result = await restoreCache(["some/path"], "nx-abc123", ["nx-"], {
lookupOnly: true
});
expect(result).toBeUndefined();
expect(cache.restoreCache).toHaveBeenCalledWith(
["some/path"],
"nx-abc123",
["nx-"],
{ lookupOnly: true },
undefined
);
});
test("keys containing slashes resolve to the full matched key", async () => {
mockStorage([
{
name: `${PREFIX}/develop/nx-abc.cache.tzst`,
updated: "2026-07-01T00:00:00Z"
}
]);
const result = await restoreCache(["some/path"], "develop/nx-xyz", [
"develop/"
]);
expect(result).toBe("develop/nx-abc");
});