1
mirror of https://github.com/jakejarvis/hugo-extended.git synced 2026-06-24 10:25:57 -04:00

test: improved OS-specific coverage (#181)

Co-authored-by: jake <jake@jarv.is>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
2026-01-07 22:56:42 -05:00
committed by GitHub
parent 0f9bca8bf5
commit f971beebfc
7 changed files with 989 additions and 39 deletions
+149
View File
@@ -0,0 +1,149 @@
import { execFileSync } from "node:child_process";
import { existsSync, lstatSync, readlinkSync, statSync } from "node:fs";
import { beforeAll, describe, expect, it } from "vitest";
import hugo, { execWithOutput, getHugoBinary } from "../../src/hugo";
import {
getBinFilename,
getBinPath,
getPkgVersion,
getReleaseFilename,
isExtended,
} from "../../src/lib/utils";
/**
* End-to-end tests for Hugo installation.
*
* These tests verify:
* - Binary is installed correctly for the current platform
* - Binary has correct permissions
* - Binary is executable and returns expected version
* - Extended version is installed where supported
*
* Note: These tests use the actual installed Hugo binary and require
* npm install/postinstall to have completed successfully.
*/
describe("Hugo Installation E2E", () => {
let binaryPath: string;
beforeAll(async () => {
// Ensure Hugo is installed
binaryPath = await hugo();
});
describe("Binary Installation", () => {
it("should have Hugo binary installed", () => {
expect(existsSync(binaryPath)).toBe(true);
});
it("should have binary at expected path", () => {
const expectedPath = getBinPath();
expect(binaryPath).toBe(expectedPath);
});
it("should have correct binary filename for platform", () => {
const expectedFilename = getBinFilename();
const actualFilename = binaryPath.split(/[\\/]/).pop();
expect(actualFilename).toBe(expectedFilename);
});
});
describe("Binary Permissions (Unix)", () => {
it.skipIf(process.platform === "win32")(
"should have executable permissions",
() => {
const stats = statSync(binaryPath);
// Check that at least owner has execute permission (0o100)
const hasExecute = (stats.mode & 0o100) !== 0;
expect(hasExecute).toBe(true);
},
);
it.skipIf(process.platform !== "darwin")(
"should be a symlink to /usr/local/bin/hugo on macOS",
() => {
const isSymlink = lstatSync(binaryPath).isSymbolicLink();
expect(isSymlink).toBe(true);
const target = readlinkSync(binaryPath);
expect(target).toBe("/usr/local/bin/hugo");
},
);
});
describe("Binary Execution", () => {
it("should execute successfully", () => {
const result = execFileSync(binaryPath, ["version"]);
expect(result).toBeTruthy();
});
it("should return version string", () => {
const result = execFileSync(binaryPath, ["version"]).toString().trim();
expect(result).toContain("hugo v");
});
it("should match package version", () => {
const pkgVersion = getPkgVersion();
const result = execFileSync(binaryPath, ["version"]).toString().trim();
expect(result).toContain(`v${pkgVersion}`);
});
it.skipIf(!isExtended(getReleaseFilename(getPkgVersion()) ?? ""))(
"should be Extended version where supported",
() => {
const result = execFileSync(binaryPath, ["version"]).toString().trim();
expect(result).toContain("+extended");
},
);
});
describe("API Integration", () => {
it("should work with default export (callable)", async () => {
const path = await hugo();
expect(path).toBe(binaryPath);
});
it("should work with getHugoBinary()", async () => {
const path = await getHugoBinary();
expect(path).toBe(binaryPath);
});
it("should work with execWithOutput()", async () => {
const { stdout } = await execWithOutput("version");
expect(stdout).toContain("hugo v");
});
it("should work with builder API", async () => {
// hugo.version() uses exec() which inherits stdio, so we use execWithOutput
const { stdout } = await execWithOutput("version");
expect(stdout).toBeTruthy();
});
});
describe("Environment Info", () => {
it("should report correct GOOS", async () => {
const { stdout } = await execWithOutput("env");
const expectedGoos =
process.platform === "win32"
? "windows"
: process.platform === "darwin"
? "darwin"
: "linux";
expect(stdout).toContain(`GOOS="${expectedGoos}"`);
});
it("should report correct GOARCH", async () => {
const { stdout } = await execWithOutput("env");
const expectedGoarch =
process.arch === "x64"
? "amd64"
: process.arch === "arm64"
? "arm64"
: process.arch;
expect(stdout).toContain(`GOARCH="${expectedGoarch}"`);
});
});
});
+209
View File
@@ -0,0 +1,209 @@
import crypto from "node:crypto";
import { afterEach, assert, beforeEach, describe, expect, it } from "vitest";
import { getArchiveType, parseChecksumFile } from "../../src/lib/install";
import { getReleaseFilename } from "../../src/lib/utils";
/**
* Unit tests for installation logic that can be tested without network calls.
* These tests verify:
* - Checksum file parsing
* - Archive type detection
* - SHA-256 computation
*/
describe("Installation Logic", () => {
describe("SHA-256 Computation", () => {
it("should correctly compute SHA-256 hash", () => {
const testData = "Hello, Hugo!";
const hash = crypto.createHash("sha256");
hash.update(Buffer.from(testData));
const digest = hash.digest("hex");
// Expected SHA-256 hash for "Hello, Hugo!"
// Computed via: echo -n "Hello, Hugo!" | sha256sum
// Cross-verified by computing with a second method below
const expectedHash =
"766a2e18bc3e2f7e217b4566b7988ca3a28e1de8cd70d995219088497a0830e5";
expect(digest).toBe(expectedHash);
expect(digest).toHaveLength(64);
// Cross-verify by computing with a fresh hash instance
const verifyHash = crypto.createHash("sha256");
verifyHash.update(testData, "utf8");
expect(verifyHash.digest("hex")).toBe(expectedHash);
});
});
describe("parseChecksumFile", () => {
it("should parse checksums file format correctly", () => {
const checksumContent = `
abc123def456 hugo_0.154.3_linux-amd64.tar.gz
def789abc012 hugo_extended_0.154.3_linux-amd64.tar.gz
ghi345jkl678 hugo_0.154.3_windows-amd64.zip
`.trim();
const checksums = parseChecksumFile(checksumContent);
expect(checksums.size).toBe(3);
expect(checksums.get("hugo_0.154.3_linux-amd64.tar.gz")).toBe(
"abc123def456",
);
expect(checksums.get("hugo_extended_0.154.3_linux-amd64.tar.gz")).toBe(
"def789abc012",
);
expect(checksums.get("hugo_0.154.3_windows-amd64.zip")).toBe(
"ghi345jkl678",
);
});
it("should find correct checksum for a given filename", () => {
const checksumContent = `
abc123def456 hugo_0.154.3_linux-amd64.tar.gz
def789abc012 hugo_extended_0.154.3_linux-amd64.tar.gz
ghi345jkl678 hugo_0.154.3_windows-amd64.zip
`;
const checksums = parseChecksumFile(checksumContent);
expect(checksums.get("hugo_extended_0.154.3_linux-amd64.tar.gz")).toBe(
"def789abc012",
);
});
it("should return undefined when filename not in checksums", () => {
const checksumContent = `
abc123def456 hugo_0.154.3_linux-amd64.tar.gz
`;
const checksums = parseChecksumFile(checksumContent);
expect(checksums.get("hugo_0.154.3_windows-amd64.zip")).toBeUndefined();
});
it("should handle empty content", () => {
const checksums = parseChecksumFile("");
expect(checksums.size).toBe(0);
});
it("should handle content with only whitespace lines", () => {
const checksums = parseChecksumFile(" \n\n \n");
expect(checksums.size).toBe(0);
});
it("should handle real-world Hugo checksums format", () => {
// Real format from Hugo releases uses two spaces between hash and filename
const realChecksumContent = `
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 hugo_0.154.3_checksums.txt
a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890 hugo_extended_0.154.3_darwin-universal.pkg
f0e9d8c7b6a5432109876543210fedcba0987654321fedcba0987654321fedc hugo_extended_0.154.3_linux-amd64.tar.gz
`;
const checksums = parseChecksumFile(realChecksumContent);
expect(checksums.size).toBe(3);
expect(checksums.get("hugo_extended_0.154.3_darwin-universal.pkg")).toBe(
"a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890",
);
});
});
describe("getArchiveType", () => {
it("should identify zip files", () => {
expect(getArchiveType("hugo_extended_0.154.3_windows-amd64.zip")).toBe(
"zip",
);
});
it("should identify tar.gz files", () => {
expect(getArchiveType("hugo_extended_0.154.3_linux-amd64.tar.gz")).toBe(
"tar.gz",
);
});
it("should identify pkg files", () => {
expect(getArchiveType("hugo_extended_0.154.3_darwin-universal.pkg")).toBe(
"pkg",
);
});
it("should return null for unknown extensions", () => {
expect(getArchiveType("hugo_0.154.3_readme.txt")).toBeNull();
expect(getArchiveType("hugo.exe")).toBeNull();
expect(getArchiveType("checksums.txt")).toBeNull();
});
it("should correctly detect archive type for all platform release filenames", () => {
// Windows x64 -> zip
expect(getArchiveType("hugo_extended_0.154.3_windows-amd64.zip")).toBe(
"zip",
);
// Windows arm64 -> zip
expect(getArchiveType("hugo_0.154.3_windows-arm64.zip")).toBe("zip");
// Linux x64 -> tar.gz
expect(getArchiveType("hugo_extended_0.154.3_linux-amd64.tar.gz")).toBe(
"tar.gz",
);
// Linux arm64 -> tar.gz
expect(getArchiveType("hugo_extended_0.154.3_linux-arm64.tar.gz")).toBe(
"tar.gz",
);
// macOS -> pkg
expect(getArchiveType("hugo_extended_0.154.3_darwin-universal.pkg")).toBe(
"pkg",
);
// FreeBSD -> tar.gz
expect(getArchiveType("hugo_0.154.3_freebsd-amd64.tar.gz")).toBe(
"tar.gz",
);
// OpenBSD -> tar.gz
expect(getArchiveType("hugo_0.154.3_openbsd-amd64.tar.gz")).toBe(
"tar.gz",
);
});
});
describe("getReleaseFilename + getArchiveType integration", () => {
let originalPlatform: NodeJS.Platform;
let originalArch: NodeJS.Architecture;
beforeEach(() => {
originalPlatform = process.platform;
originalArch = process.arch;
});
afterEach(() => {
Object.defineProperty(process, "platform", { value: originalPlatform });
Object.defineProperty(process, "arch", { value: originalArch });
});
it("should return zip for Windows release filenames", () => {
Object.defineProperty(process, "platform", { value: "win32" });
Object.defineProperty(process, "arch", { value: "x64" });
const filename = getReleaseFilename("0.154.3");
assert(filename !== null, "Expected Windows x64 to have a release file");
expect(getArchiveType(filename)).toBe("zip");
});
it("should return tar.gz for Linux release filenames", () => {
Object.defineProperty(process, "platform", { value: "linux" });
Object.defineProperty(process, "arch", { value: "x64" });
const filename = getReleaseFilename("0.154.3");
assert(filename !== null, "Expected Linux x64 to have a release file");
expect(getArchiveType(filename)).toBe("tar.gz");
});
it("should return pkg for macOS release filenames", () => {
Object.defineProperty(process, "platform", { value: "darwin" });
Object.defineProperty(process, "arch", { value: "arm64" });
const filename = getReleaseFilename("0.154.3");
assert(filename !== null, "Expected macOS arm64 to have a release file");
expect(getArchiveType(filename)).toBe("pkg");
});
});
});
+192
View File
@@ -0,0 +1,192 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
getBinFilename,
getChecksumFilename,
getReleaseFilename,
getReleaseUrl,
isExtended,
} from "../../src/lib/utils";
describe("utils", () => {
describe("getBinFilename", () => {
let originalPlatform: NodeJS.Platform;
beforeEach(() => {
originalPlatform = process.platform;
});
afterEach(() => {
Object.defineProperty(process, "platform", { value: originalPlatform });
});
it("should return hugo.exe on Windows", () => {
Object.defineProperty(process, "platform", { value: "win32" });
expect(getBinFilename()).toBe("hugo.exe");
});
it("should return hugo on Linux", () => {
Object.defineProperty(process, "platform", { value: "linux" });
expect(getBinFilename()).toBe("hugo");
});
it("should return hugo on macOS", () => {
Object.defineProperty(process, "platform", { value: "darwin" });
expect(getBinFilename()).toBe("hugo");
});
it("should return hugo on other platforms", () => {
Object.defineProperty(process, "platform", { value: "freebsd" });
expect(getBinFilename()).toBe("hugo");
});
});
describe("getReleaseFilename", () => {
let originalPlatform: NodeJS.Platform;
let originalArch: NodeJS.Architecture;
beforeEach(() => {
originalPlatform = process.platform;
originalArch = process.arch;
});
afterEach(() => {
Object.defineProperty(process, "platform", { value: originalPlatform });
Object.defineProperty(process, "arch", { value: originalArch });
});
describe("macOS", () => {
it("should return universal pkg for darwin x64", () => {
Object.defineProperty(process, "platform", { value: "darwin" });
Object.defineProperty(process, "arch", { value: "x64" });
expect(getReleaseFilename("0.154.3")).toBe(
"hugo_extended_0.154.3_darwin-universal.pkg",
);
});
it("should return universal pkg for darwin arm64", () => {
Object.defineProperty(process, "platform", { value: "darwin" });
Object.defineProperty(process, "arch", { value: "arm64" });
expect(getReleaseFilename("0.154.3")).toBe(
"hugo_extended_0.154.3_darwin-universal.pkg",
);
});
});
describe("Windows", () => {
it("should return extended zip for win32 x64", () => {
Object.defineProperty(process, "platform", { value: "win32" });
Object.defineProperty(process, "arch", { value: "x64" });
expect(getReleaseFilename("0.154.3")).toBe(
"hugo_extended_0.154.3_windows-amd64.zip",
);
});
it("should return vanilla zip for win32 arm64 (no extended support)", () => {
Object.defineProperty(process, "platform", { value: "win32" });
Object.defineProperty(process, "arch", { value: "arm64" });
expect(getReleaseFilename("0.154.3")).toBe(
"hugo_0.154.3_windows-arm64.zip",
);
});
});
describe("Linux", () => {
it("should return extended tar.gz for linux x64", () => {
Object.defineProperty(process, "platform", { value: "linux" });
Object.defineProperty(process, "arch", { value: "x64" });
expect(getReleaseFilename("0.154.3")).toBe(
"hugo_extended_0.154.3_linux-amd64.tar.gz",
);
});
it("should return extended tar.gz for linux arm64", () => {
Object.defineProperty(process, "platform", { value: "linux" });
Object.defineProperty(process, "arch", { value: "arm64" });
expect(getReleaseFilename("0.154.3")).toBe(
"hugo_extended_0.154.3_linux-arm64.tar.gz",
);
});
});
describe("BSD", () => {
it("should return vanilla tar.gz for freebsd x64", () => {
Object.defineProperty(process, "platform", { value: "freebsd" });
Object.defineProperty(process, "arch", { value: "x64" });
expect(getReleaseFilename("0.154.3")).toBe(
"hugo_0.154.3_freebsd-amd64.tar.gz",
);
});
it("should return vanilla tar.gz for openbsd x64", () => {
Object.defineProperty(process, "platform", { value: "openbsd" });
Object.defineProperty(process, "arch", { value: "x64" });
expect(getReleaseFilename("0.154.3")).toBe(
"hugo_0.154.3_openbsd-amd64.tar.gz",
);
});
});
describe("unsupported platforms", () => {
it("should return null for unsupported platform", () => {
Object.defineProperty(process, "platform", { value: "sunos" });
Object.defineProperty(process, "arch", { value: "x64" });
expect(getReleaseFilename("0.154.3")).toBeNull();
});
it("should return null for unsupported arch on linux", () => {
Object.defineProperty(process, "platform", { value: "linux" });
Object.defineProperty(process, "arch", { value: "ia32" });
expect(getReleaseFilename("0.154.3")).toBeNull();
});
it("should return null for unsupported arch on freebsd", () => {
Object.defineProperty(process, "platform", { value: "freebsd" });
Object.defineProperty(process, "arch", { value: "arm64" });
expect(getReleaseFilename("0.154.3")).toBeNull();
});
});
});
describe("getChecksumFilename", () => {
it("should return correct checksum filename", () => {
expect(getChecksumFilename("0.154.3")).toBe("hugo_0.154.3_checksums.txt");
});
it("should handle different version formats", () => {
expect(getChecksumFilename("1.0.0")).toBe("hugo_1.0.0_checksums.txt");
expect(getChecksumFilename("0.100.0")).toBe("hugo_0.100.0_checksums.txt");
});
});
describe("isExtended", () => {
it("should return true for extended releases", () => {
expect(isExtended("hugo_extended_0.154.3_linux-amd64.tar.gz")).toBe(true);
expect(isExtended("hugo_extended_0.154.3_darwin-universal.pkg")).toBe(
true,
);
expect(isExtended("hugo_extended_0.154.3_windows-amd64.zip")).toBe(true);
});
it("should return false for vanilla releases", () => {
expect(isExtended("hugo_0.154.3_windows-arm64.zip")).toBe(false);
expect(isExtended("hugo_0.154.3_freebsd-amd64.tar.gz")).toBe(false);
expect(isExtended("hugo_0.154.3_openbsd-amd64.tar.gz")).toBe(false);
});
});
describe("getReleaseUrl", () => {
it("should return correct GitHub release URL", () => {
expect(
getReleaseUrl("0.154.3", "hugo_extended_0.154.3_linux-amd64.tar.gz"),
).toBe(
"https://github.com/gohugoio/hugo/releases/download/v0.154.3/hugo_extended_0.154.3_linux-amd64.tar.gz",
);
});
it("should work with checksum files", () => {
expect(getReleaseUrl("0.154.3", "hugo_0.154.3_checksums.txt")).toBe(
"https://github.com/gohugoio/hugo/releases/download/v0.154.3/hugo_0.154.3_checksums.txt",
);
});
});
});