Files
headplane/tests/unit/headscale/server-version.test.ts
T
2026-07-04 00:07:53 -04:00

108 lines
3.5 KiB
TypeScript

import { describe, expect, test } from "vitest";
import { capabilitiesFor } from "~/server/headscale/api/capabilities";
import {
formatServerVersion,
gte,
parseServerVersion,
} from "~/server/headscale/api/server-version";
describe("parseServerVersion", () => {
test("parses release versions with and without a leading v", () => {
expect(parseServerVersion("0.28.0")).toMatchObject({
major: 0,
minor: 28,
patch: 0,
prerelease: undefined,
unknown: false,
});
expect(parseServerVersion("v0.27.1")).toMatchObject({
major: 0,
minor: 27,
patch: 1,
unknown: false,
});
});
test("captures prerelease and build metadata", () => {
const v = parseServerVersion("v0.28.0-beta.1+abcdef");
expect(v.major).toBe(0);
expect(v.minor).toBe(28);
expect(v.patch).toBe(0);
expect(v.prerelease).toBe("beta.1");
expect(v.build).toBe("abcdef");
expect(v.unknown).toBe(false);
});
test("flags unparseable versions (e.g. dev builds)", () => {
const v = parseServerVersion("dev");
expect(v.unknown).toBe(true);
expect(v.raw).toBe("dev");
});
});
describe("gte", () => {
test("compares plain versions correctly", () => {
const v = parseServerVersion("0.28.0");
expect(gte(v, "0.27.0")).toBe(true);
expect(gte(v, "0.28.0")).toBe(true);
expect(gte(v, "0.29.0")).toBe(false);
});
test("ignores prerelease tags so betas get modern capabilities", () => {
// 0.28.0-beta.1 ships every wire format change that 0.28.0 does, so
// strict semver (where 0.28.0-beta.1 < 0.28.0) would lock beta users
// out of features their server actually supports.
const beta = parseServerVersion("0.28.0-beta.1");
expect(gte(beta, "0.28.0")).toBe(true);
expect(gte(beta, "0.27.0")).toBe(true);
});
test("unknown versions are treated as the newest known release", () => {
const dev = parseServerVersion("dev");
expect(gte(dev, "9.9.9")).toBe(true);
});
});
describe("formatServerVersion", () => {
test("renders release and prerelease versions without a leading v", () => {
expect(formatServerVersion(parseServerVersion("v0.28.0"))).toBe("0.28.0");
expect(formatServerVersion(parseServerVersion("0.28.0-beta.1"))).toBe("0.28.0-beta.1");
});
test("falls back to the raw string for unknown versions", () => {
expect(formatServerVersion(parseServerVersion("dev"))).toBe("dev");
});
});
describe("capabilitiesFor", () => {
test("0.28.0 enables every modern flag", () => {
const caps = capabilitiesFor(parseServerVersion("0.28.0"));
expect(caps).toEqual({
preAuthKeysHaveStableIds: true,
nodeTagsAreFlat: true,
nodeOwnerIsImmutable: true,
registerKeyIncludesAuthReqPrefix: false,
});
});
test("0.28.0-beta.1 matches 0.28.0 capabilities (prerelease gating)", () => {
expect(capabilitiesFor(parseServerVersion("0.28.0-beta.1"))).toEqual(
capabilitiesFor(parseServerVersion("0.28.0")),
);
});
test("0.29.0 enables the prefixed AuthID register key", () => {
const caps = capabilitiesFor(parseServerVersion("0.29.0"));
expect(caps.registerKeyIncludesAuthReqPrefix).toBe(true);
});
test("0.27.1 lacks every 0.28-gated capability", () => {
const caps = capabilitiesFor(parseServerVersion("0.27.1"));
expect(caps.preAuthKeysHaveStableIds).toBe(false);
expect(caps.nodeTagsAreFlat).toBe(false);
expect(caps.nodeOwnerIsImmutable).toBe(false);
expect(caps.registerKeyIncludesAuthReqPrefix).toBe(false);
});
});