feat: initial
This commit is contained in:
commit
b04a255b4c
17 changed files with 6509 additions and 0 deletions
232
tests/action.test.js
Normal file
232
tests/action.test.js
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
import { afterAll, beforeAll, beforeEach, afterEach, describe, vi, it, expect } from "vitest";
|
||||
import { GenericContainer, Wait } from "testcontainers";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import fs from "fs/promises";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import crypto from "crypto";
|
||||
|
||||
import { restore } from "../src/restore.js";
|
||||
import { save } from "../src/save.js";
|
||||
|
||||
vi.mock("@actions/core");
|
||||
|
||||
const fileSha256 = async (filePath) => {
|
||||
const buf = await fs.readFile(filePath);
|
||||
return crypto.createHash("sha256").update(buf).digest("hex");
|
||||
};
|
||||
|
||||
describe("S3 Cache Action Integration Tests", () => {
|
||||
// defaults
|
||||
const s3bucket = "integration-test";
|
||||
const s3region = "us-east-1";
|
||||
|
||||
// env leak prevention
|
||||
const originalEnv = process.env;
|
||||
|
||||
// endpoint generated by testcontainers
|
||||
let s3mockContainer;
|
||||
let s3endpoint;
|
||||
|
||||
// tmpdirs used by runner
|
||||
let runnerWorkspace;
|
||||
let runnerTemp;
|
||||
|
||||
// tmpdirs and files used by action
|
||||
let testDataFolder;
|
||||
let testDataFile;
|
||||
|
||||
// mocked inputs and states
|
||||
let inputs;
|
||||
let states;
|
||||
|
||||
beforeAll(async () => {
|
||||
process.env = { ...originalEnv };
|
||||
|
||||
// gnutar fix for macos
|
||||
if (os.platform() == "darwin") {
|
||||
const gnuTarPath =
|
||||
os.arch() === "arm64" ? "/opt/homebrew/opt/gnu-tar/libexec/gnubin" : "/usr/local/opt/gnu-tar/libexec/gnubin";
|
||||
|
||||
process.env.PATH = `${gnuTarPath}:${process.env.PATH}`;
|
||||
}
|
||||
|
||||
if (process.env.S3_ENDPOINT) {
|
||||
s3endpoint = process.env.S3_ENDPOINT;
|
||||
} else {
|
||||
// start testcontainers
|
||||
s3mockContainer = await new GenericContainer("adobe/s3mock:5.0.0")
|
||||
.withExposedPorts(9090)
|
||||
.withEnvironment({
|
||||
COM_ADOBE_TESTING_S3MOCK_STORE_INITIAL_BUCKETS: s3bucket,
|
||||
COM_ADOBE_TESTING_S3MOCK_STORE_REGION: s3region,
|
||||
})
|
||||
.withWaitStrategy(Wait.forLogMessage(/.*Started S3MockApplication\.Companion in.*/))
|
||||
.start();
|
||||
|
||||
s3endpoint = `http://${s3mockContainer.getHost()}:${s3mockContainer.getMappedPort(9090)}`;
|
||||
}
|
||||
|
||||
// setup runner environment
|
||||
const workspacePrefix = path.join(os.tmpdir(), "runner-workspace-");
|
||||
const tempPrefix = path.join(os.tmpdir(), "runner-temp-");
|
||||
|
||||
runnerWorkspace = await fs.mkdtemp(workspacePrefix);
|
||||
runnerTemp = await fs.mkdtemp(tempPrefix);
|
||||
|
||||
process.env.GITHUB_WORKSPACE = runnerWorkspace;
|
||||
process.env.RUNNER_TEMP = runnerTemp;
|
||||
}, 60000);
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// create new data folder for each test run
|
||||
const testDataPrefix = path.join(os.tmpdir(), "runner-data-");
|
||||
testDataFolder = await fs.mkdtemp(testDataPrefix);
|
||||
|
||||
// default mock inputs
|
||||
inputs = {
|
||||
key: `test-${crypto.randomUUID()}`,
|
||||
path: testDataFolder,
|
||||
"s3-bucket": s3bucket,
|
||||
"s3-endpoint": s3endpoint,
|
||||
"s3-access-key": "mock-access",
|
||||
"s3-secret-key": "mock-secret",
|
||||
"s3-region": s3region,
|
||||
"lookup-only": "false",
|
||||
"fail-on-cache-miss": "false",
|
||||
"restore-keys": "",
|
||||
};
|
||||
|
||||
// mock state
|
||||
states = {};
|
||||
|
||||
// mock function calls
|
||||
vi.mocked(core.getInput).mockImplementation((name) => inputs[name] || "");
|
||||
vi.mocked(core.getMultilineInput).mockImplementation((name) => {
|
||||
if (name === "path") {
|
||||
return [inputs["path"]];
|
||||
}
|
||||
|
||||
if (name === "restore-keys") {
|
||||
return inputs["restore-keys"] ? inputs["restore-keys"].split("\n") : [];
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
vi.mocked(core.getState).mockImplementation((name) => states[name] || "");
|
||||
vi.mocked(core.saveState).mockImplementation((name, value) => {
|
||||
states[name] = value;
|
||||
});
|
||||
|
||||
// generate test data file with random data
|
||||
testDataFile = path.join(testDataFolder, `${crypto.randomUUID()}`);
|
||||
await fs.writeFile(testDataFile, crypto.randomBytes(4 * 1024 * 1024));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await fs.rm(testDataFolder, { force: true, recursive: true });
|
||||
});
|
||||
|
||||
it("should result in cache-miss when key is not found", async () => {
|
||||
await restore();
|
||||
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-hit", "false");
|
||||
expect(core.saveState).toHaveBeenCalledWith("exactMatch", "false");
|
||||
});
|
||||
|
||||
it("should save the cache successfully", async () => {
|
||||
await save();
|
||||
|
||||
expect(core.info).toHaveBeenCalledWith("Upload complete");
|
||||
});
|
||||
|
||||
it("should restore matched cache exactly", async () => {
|
||||
const originalChecksum = await fileSha256(testDataFile);
|
||||
await save();
|
||||
|
||||
await fs.rm(testDataFile);
|
||||
|
||||
await restore();
|
||||
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-hit", "true");
|
||||
expect(core.saveState).toHaveBeenCalledWith("exactMatch", "true");
|
||||
|
||||
const restoredChecksum = await fileSha256(testDataFile);
|
||||
expect(restoredChecksum).toBe(originalChecksum);
|
||||
});
|
||||
|
||||
it("should skip save if exact match exists", async () => {
|
||||
states["exactMatch"] = "true";
|
||||
|
||||
await save();
|
||||
|
||||
expect(core.info).toHaveBeenCalledWith("Exact match found, skipping cache upload");
|
||||
});
|
||||
|
||||
it("should match prefix using restore-keys", async () => {
|
||||
inputs["key"] = "test-key-12345";
|
||||
|
||||
const originalChecksum = await fileSha256(testDataFile);
|
||||
await save();
|
||||
|
||||
await fs.rm(testDataFile);
|
||||
|
||||
inputs["key"] = "completely-different-key";
|
||||
inputs["restore-keys"] = "test-key-";
|
||||
|
||||
await restore();
|
||||
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-hit", "false");
|
||||
expect(core.saveState).toHaveBeenCalledWith("exactMatch", "false");
|
||||
|
||||
const restoredChecksum = await fileSha256(testDataFile);
|
||||
expect(restoredChecksum).toBe(originalChecksum);
|
||||
});
|
||||
|
||||
it("should respect lookup-only and not extract files", async () => {
|
||||
await save();
|
||||
await fs.rm(testDataFile);
|
||||
|
||||
inputs["lookup-only"] = "true";
|
||||
await restore();
|
||||
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-hit", "true");
|
||||
expect(core.saveState).toHaveBeenCalledWith("exactMatch", "true");
|
||||
|
||||
await expect(fs.readFile(testDataFile)).rejects.toThrow(/ENOENT/);
|
||||
});
|
||||
|
||||
it("should respect fail-on-cache-miss", async () => {
|
||||
inputs["fail-on-cache-miss"] = "true";
|
||||
|
||||
await restore();
|
||||
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-hit", "false");
|
||||
expect(core.setFailed).toHaveBeenCalledWith("No matching cache key found.");
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
// reset env
|
||||
process.env = originalEnv;
|
||||
|
||||
if (s3mockContainer) {
|
||||
await s3mockContainer.stop();
|
||||
}
|
||||
|
||||
if (runnerWorkspace) {
|
||||
await fs.rm(runnerWorkspace, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
if (runnerTemp) {
|
||||
await fs.rm(runnerTemp, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
if (testDataFolder) {
|
||||
await fs.rm(testDataFolder, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue