mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-17 14:52:32 +00:00
Run CLI Docker validation on native architectures and publish tested images
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { imageReferences, summariseResults } from "./cli-docker-artifacts.mjs";
|
||||
|
||||
const helper = fileURLToPath(new URL("./cli-docker-artifacts.mjs", import.meta.url));
|
||||
const temporaryDirectories: string[] = [];
|
||||
const amd64Digest = `sha256:${"a".repeat(64)}`;
|
||||
const arm64Digest = `sha256:${"b".repeat(64)}`;
|
||||
|
||||
afterEach(() => {
|
||||
for (const directory of temporaryDirectories.splice(0)) {
|
||||
rmSync(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
function makeTemporaryDirectory(): string {
|
||||
const directory = mkdtempSync(join(tmpdir(), "livesync-cli-docker-artifacts-"));
|
||||
temporaryDirectories.push(directory);
|
||||
return directory;
|
||||
}
|
||||
|
||||
function runCli(args: string[]) {
|
||||
return spawnSync(process.execPath, [helper, ...args], { encoding: "utf8" });
|
||||
}
|
||||
|
||||
function writeJson(directory: string, name: string, value: unknown): void {
|
||||
writeFileSync(join(directory, name), `${JSON.stringify(value)}\n`, "utf8");
|
||||
}
|
||||
|
||||
describe("CLI Docker artifact aggregation", () => {
|
||||
it.each([
|
||||
[
|
||||
[
|
||||
{ arch: "amd64", e2e_passed: false },
|
||||
{ arch: "arm64", e2e_passed: true },
|
||||
],
|
||||
false,
|
||||
],
|
||||
[
|
||||
[
|
||||
{ arch: "arm64", e2e_passed: true },
|
||||
{ arch: "amd64", e2e_passed: false },
|
||||
],
|
||||
false,
|
||||
],
|
||||
])("summarises mixed results independent of record order", (records, expected) => {
|
||||
expect(summariseResults(records)).toBe(expected);
|
||||
});
|
||||
|
||||
it("reports success only when both architecture results pass", () => {
|
||||
expect(
|
||||
summariseResults([
|
||||
{ arch: "amd64", e2e_passed: true },
|
||||
{ arch: "arm64", e2e_passed: true },
|
||||
])
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects malformed, missing, and duplicate result records", () => {
|
||||
expect(() =>
|
||||
summariseResults([
|
||||
{ arch: "amd64", e2e_passed: "true" },
|
||||
{ arch: "arm64", e2e_passed: true },
|
||||
])
|
||||
).toThrow(/boolean/);
|
||||
expect(() => summariseResults([{ arch: "amd64", e2e_passed: true }])).toThrow(/Exactly one/);
|
||||
expect(() =>
|
||||
summariseResults([
|
||||
{ arch: "amd64", e2e_passed: true },
|
||||
{ arch: "amd64", e2e_passed: false },
|
||||
])
|
||||
).toThrow(/Duplicate/);
|
||||
});
|
||||
|
||||
it("builds deterministic architecture-ordered references and rejects invalid or duplicate digests", () => {
|
||||
const records = [
|
||||
{ arch: "arm64", digest: arm64Digest },
|
||||
{ arch: "amd64", digest: amd64Digest },
|
||||
];
|
||||
expect(imageReferences("ghcr.io/example/cli", records)).toEqual([
|
||||
`ghcr.io/example/cli@${amd64Digest}`,
|
||||
`ghcr.io/example/cli@${arm64Digest}`,
|
||||
]);
|
||||
expect(() =>
|
||||
imageReferences("ghcr.io/example/cli", [
|
||||
{ arch: "amd64", digest: "sha256:BAD" },
|
||||
{ arch: "arm64", digest: arm64Digest },
|
||||
])
|
||||
).toThrow(/Digest/);
|
||||
expect(() =>
|
||||
imageReferences("ghcr.io/example/cli", [
|
||||
{ arch: "amd64", digest: amd64Digest },
|
||||
{ arch: "arm64", digest: amd64Digest },
|
||||
])
|
||||
).toThrow(/distinct/);
|
||||
expect(() => imageReferences("../../escape", records)).toThrow(/canonical image/);
|
||||
});
|
||||
|
||||
it("rejects extra JSON files and filenames whose records name another architecture", () => {
|
||||
const directory = makeTemporaryDirectory();
|
||||
writeJson(directory, "amd64.json", { arch: "amd64", e2e_passed: true });
|
||||
writeJson(directory, "arm64.json", { arch: "arm64", e2e_passed: true });
|
||||
writeJson(directory, "extra.json", { arch: "arm64", e2e_passed: false });
|
||||
expect(runCli(["summary", directory]).status).toBe(1);
|
||||
|
||||
rmSync(join(directory, "extra.json"));
|
||||
writeJson(directory, "amd64.json", { arch: "arm64", e2e_passed: true });
|
||||
expect(runCli(["summary", directory]).status).toBe(1);
|
||||
});
|
||||
|
||||
it("prints a false summary successfully and fails when either result file is missing", () => {
|
||||
const directory = makeTemporaryDirectory();
|
||||
const results = join(directory, "results");
|
||||
expect(runCli(["record-result", "amd64", "failure", results]).status).toBe(0);
|
||||
expect(runCli(["record-result", "arm64", "success", results]).status).toBe(0);
|
||||
|
||||
const summary = runCli(["summary", results]);
|
||||
expect(summary.status, summary.stderr).toBe(0);
|
||||
expect(summary.stdout).toBe("e2e_passed=false\n");
|
||||
|
||||
const missing = join(directory, "missing");
|
||||
mkdirSync(missing);
|
||||
expect(runCli(["summary", missing]).status).toBe(1);
|
||||
});
|
||||
|
||||
it("records results and digests without overwriting and prints references in canonical order", () => {
|
||||
const directory = makeTemporaryDirectory();
|
||||
const results = join(directory, "results");
|
||||
const digests = join(directory, "digests");
|
||||
|
||||
expect(runCli(["record-result", "arm64", "success", results]).status).toBe(0);
|
||||
expect(runCli(["record-result", "amd64", "skipped", results]).status).toBe(0);
|
||||
expect(runCli(["record-result", "amd64", "success", results]).status).toBe(1);
|
||||
expect(JSON.parse(readFileSync(join(results, "amd64.json"), "utf8"))).toEqual({
|
||||
arch: "amd64",
|
||||
e2e_passed: false,
|
||||
});
|
||||
|
||||
expect(runCli(["record-digest", "arm64", arm64Digest, digests]).status).toBe(0);
|
||||
expect(runCli(["record-digest", "amd64", amd64Digest, digests]).status).toBe(0);
|
||||
const invalid = join(directory, "invalid");
|
||||
expect(runCli(["record-digest", "amd64", "sha256:BAD", invalid]).status).toBe(1);
|
||||
expect(existsSync(invalid)).toBe(false);
|
||||
|
||||
const references = runCli(["references", "ghcr.io/example/cli", digests]);
|
||||
expect(references.status, references.stderr).toBe(0);
|
||||
expect(references.stdout).toBe(`ghcr.io/example/cli@${amd64Digest}\nghcr.io/example/cli@${arm64Digest}\n`);
|
||||
|
||||
const traversal = runCli(["record-result", "../escape", "success", join(directory, "nested")]);
|
||||
expect(traversal.status).toBe(1);
|
||||
expect(readdirSync(directory)).not.toContain("escape.json");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user