scripts/prepare-cloudflared.test.mjs
Rama main · réplica de lectura del repositorio original.
import { afterEach, describe, expect, it, vi } from "vitest";
import { readFileSync } from "node:fs";
import {
CLOUDFLARED_ASSETS,
CLOUDFLARED_VERSION,
executableTarget,
parsePrepareCloudflaredArgs,
releaseBytes,
sha256,
targetForCurrentHost,
targetsForHost,
targetsForPreparation,
verifyPinnedBinary,
verifySha256,
} from "./prepare-cloudflared.mjs";
describe("cloudflared download retries", () => {
afterEach(() => {
vi.useRealTimers();
vi.unstubAllGlobals();
vi.unstubAllEnvs();
vi.restoreAllMocks();
});
function mockDownload() {
vi.useFakeTimers();
vi.stubEnv("OMB_CLOUDFLARED_ARCHIVE_DIR", "");
vi.spyOn(console, "warn").mockImplementation(() => {});
const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);
return fetchMock;
}
it("recovers from an HTTP 504 and verifies the recovered bytes normally", async () => {
const fetchMock = mockDownload();
const bytes = Buffer.from("downloaded release");
fetchMock.mockResolvedValueOnce(new Response("gateway timeout", { status: 504 }))
.mockResolvedValueOnce(new Response(bytes));
const download = releaseBytes({ name: "cloudflared-linux-amd64" });
await vi.runAllTimersAsync();
const result = await download;
expect(result).toEqual(bytes);
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(verifySha256(result, sha256(bytes))).toBe(sha256(bytes));
expect(() => verifySha256(result, sha256(Buffer.from("other release")))).toThrow(/SHA-256 verification/);
});
it("retries a network error and an interrupted response body", async () => {
const fetchMock = mockDownload();
fetchMock.mockRejectedValueOnce(new TypeError("fetch failed"))
.mockResolvedValueOnce({ ok: true, arrayBuffer: async () => { throw new TypeError("terminated"); } })
.mockResolvedValueOnce(new Response("complete"));
const download = releaseBytes({ name: "cloudflared-linux-amd64" });
await vi.runAllTimersAsync();
expect(await download).toEqual(Buffer.from("complete"));
expect(fetchMock).toHaveBeenCalledTimes(3);
});
it("stops after three transient failures", async () => {
const fetchMock = mockDownload();
fetchMock.mockImplementation(async () => new Response("unavailable", { status: 503 }));
const result = expect(releaseBytes({ name: "cloudflared-linux-amd64" })).rejects.toThrow(/HTTP 503/);
await vi.runAllTimersAsync();
await result;
expect(fetchMock).toHaveBeenCalledTimes(3);
});
it("fails immediately for a missing pinned asset", async () => {
const fetchMock = mockDownload();
fetchMock.mockResolvedValue(new Response("not found", { status: 404 }));
await expect(releaseBytes({ name: "missing" })).rejects.toThrow(/HTTP 404/);
expect(fetchMock).toHaveBeenCalledTimes(1);
});
});
const PINNED_ASSETS = {
"darwin-arm64": {
name: "cloudflared-darwin-arm64.tgz",
sha256: "1165d14b6f6360a3b8c283d7d1100024d6ec7cd9bc9e32519e96150645f3ec55",
binarySha256: "f249139e0541fa589c987397d867cc16c83ab1236d31eb6782f2e4552ee9cf80",
archive: true,
},
"darwin-x64": {
name: "cloudflared-darwin-amd64.tgz",
sha256: "81b7b85f2376815c4f59b1aa323887b62e16ce8b67555c2e1577dc23f2a82000",
binarySha256: "947e2942d6a59922d80190d23b12f3ade8da16e23e3bd937182a48993b7c00db",
archive: true,
},
"linux-x64": {
name: "cloudflared-linux-amd64",
sha256: "053b80b0a23031a038fdbf3c073f59f82b8f1f152aa67971d7383ffafc0dbab0",
binarySha256: "053b80b0a23031a038fdbf3c073f59f82b8f1f152aa67971d7383ffafc0dbab0",
archive: false,
},
"linux-arm64": {
name: "cloudflared-linux-arm64",
sha256: "89297ebfb9e8c99c0dc013a1140601453cf3798d8306c5591651f4a3f5210be8",
binarySha256: "89297ebfb9e8c99c0dc013a1140601453cf3798d8306c5591651f4a3f5210be8",
archive: false,
},
"win32-x64": {
name: "cloudflared-windows-amd64.exe",
sha256: "76b2805253d0d6ffacb092a7acfbee99e7af6917835c0c5410e23f3ef5052da2",
binarySha256: "76b2805253d0d6ffacb092a7acfbee99e7af6917835c0c5410e23f3ef5052da2",
archive: false,
},
};
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
function executableFixture(target) {
const bytes = Buffer.alloc(128);
if (target === "darwin-arm64" || target === "darwin-x64") {
bytes.writeUInt32LE(0xfeedfacf, 0);
bytes.writeUInt32LE(target === "darwin-arm64" ? 0x0100000c : 0x01000007, 4);
} else if (target === "linux-x64" || target === "linux-arm64") {
Buffer.from([0x7f, 0x45, 0x4c, 0x46, 2, 1]).copy(bytes);
bytes.writeUInt16LE(target === "linux-arm64" ? 183 : 62, 18);
} else if (target === "win32-x64") {
bytes.write("MZ", 0, "ascii");
bytes.writeUInt32LE(0x40, 0x3c);
bytes.write("PE\0\0", 0x40, "binary");
bytes.writeUInt16LE(0x8664, 0x44);
}
return bytes;
}
describe("pinned cloudflared packaging", () => {
it("stages both macOS architectures and only shipped desktop targets elsewhere", () => {
expect(targetsForHost("darwin")).toEqual(["darwin-arm64", "darwin-x64"]);
expect(targetsForHost("linux")).toEqual(["linux-x64"]);
expect(targetsForHost("win32")).toEqual(["win32-x64"]);
expect(() => targetsForHost("freebsd")).toThrow(/unsupported/);
});
it("stages the exact current target for development and self-hosting", () => {
expect(targetForCurrentHost("darwin", "arm64")).toBe("darwin-arm64");
expect(targetForCurrentHost("darwin", "x64")).toBe("darwin-x64");
expect(targetForCurrentHost("linux", "x64")).toBe("linux-x64");
expect(targetForCurrentHost("linux", "arm64")).toBe("linux-arm64");
expect(targetForCurrentHost("win32", "x64")).toBe("win32-x64");
expect(targetsForPreparation({ current: true, platform: "darwin", arch: "arm64" })).toEqual([
"darwin-arm64",
]);
expect(targetsForPreparation({ current: false, platform: "darwin", arch: "arm64" })).toEqual([
"darwin-arm64",
"darwin-x64",
]);
expect(targetsForPreparation({ current: true, platform: "linux", arch: "arm64" })).toEqual([
"linux-arm64",
]);
expect(() => targetForCurrentHost("linux", "arm")).toThrow(/unsupported/);
});
it("accepts only the documented current-target CLI option", () => {
expect(parsePrepareCloudflaredArgs([])).toEqual({ current: false });
expect(parsePrepareCloudflaredArgs(["--current"])).toEqual({ current: true });
expect(() => parsePrepareCloudflaredArgs(["--all"])).toThrow(/Usage:/);
expect(() => parsePrepareCloudflaredArgs(["--current", "--current"])).toThrow(/Usage:/);
// `openmausbot serve --tunnel` stages into its data dir
expect(parsePrepareCloudflaredArgs(["--current", "--root", "/srv/omb"])).toEqual({ current: true, root: "/srv/omb" });
expect(() => parsePrepareCloudflaredArgs(["--root"])).toThrow(/Usage:/);
expect(() => parsePrepareCloudflaredArgs(["--root", "/a", "--root", "/b"])).toThrow(/Usage:/);
});
it("stages the current target for development without narrowing package preparation", () => {
expect(packageJson.scripts["dev:desktop"]).toBe(
"node scripts/prepare-cloudflared.mjs --current && electron .",
);
expect(packageJson.scripts["build:cloudflared"]).toBe(
"node scripts/prepare-cloudflared.mjs",
);
});
it("pins a complete release asset and digest for every supported target", () => {
expect(CLOUDFLARED_VERSION).toBe("2026.10.3");
expect(CLOUDFLARED_ASSETS).toEqual(PINNED_ASSETS);
});
it("rejects altered release bytes", () => {
const payload = Buffer.from("official bytes");
const digest = sha256(payload);
expect(verifySha256(payload, digest)).toBe(digest);
expect(() => verifySha256(Buffer.from("altered"), digest)).toThrow(/SHA-256 verification/);
});
it("recognizes only the executable formats and architectures we ship", () => {
for (const target of Object.keys(PINNED_ASSETS)) {
expect(executableTarget(executableFixture(target))).toBe(target);
}
expect(() => executableTarget(Buffer.from("not an executable"))).toThrow(/unsupported/);
});
it("checks architecture before accepting a pinned executable", () => {
const bytes = executableFixture("darwin-arm64");
expect(() => verifyPinnedBinary(bytes, "darwin-x64")).toThrow(/architecture mismatch/);
expect(() => verifyPinnedBinary(bytes, "darwin-arm64")).toThrow(/SHA-256 verification/);
});
it.each(["linux-x64", "linux-arm64"])("rejects the wrong architecture and unpinned bytes for %s", (target) => {
const bytes = executableFixture(target);
const other = target === "linux-arm64" ? "linux-x64" : "linux-arm64";
expect(() => verifyPinnedBinary(bytes, other)).toThrow(/architecture mismatch/);
expect(() => verifyPinnedBinary(bytes, target)).toThrow(/SHA-256 verification/);
});
it("does not mistake 32-bit, big-endian, truncated or unsupported ELF files for ARM64", () => {
const arm32 = executableFixture("linux-arm64");
arm32[4] = 1;
const bigEndian = executableFixture("linux-arm64");
bigEndian[5] = 2;
bigEndian.writeUInt16BE(183, 18);
const otherMachine = executableFixture("linux-arm64");
otherMachine.writeUInt16LE(40, 18); // EM_ARM is not AArch64.
const truncated = executableFixture("linux-arm64").subarray(0, 19);
for (const bytes of [arm32, bigEndian, otherMachine, truncated]) {
expect(() => executableTarget(bytes)).toThrow(/unsupported/);
}
});
});