/*
RFC-0930: unit tests for leitstand.verify command — fetches build-identity.json from live channels, compares distTreeHash cross-channel, optionally compares against local system-state.yaml.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- RFC-0930: initial tests for leitstand.verify command covering all-match success, hash drift, unreachable channel, local mismatch, single-channel filter, access-protected channel, 404, no deployment config, --no-compare-local, and fetch timeout.
- RFC-1188: Web Bot Auth outbound identity: wba-sign primitives, directory emit+sweep, AGS-WBA-01, leitstand signed fetches
*/
import { describe, expect, it, vi, afterEach } from "vitest";
import { createDefaultIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io";
vi.mock("../../sternsystem/registry-io.ts", () => ({
readSystemConfigSmart: vi.fn(),
readSystemStateSmart: vi.fn(),
resolveCacheClonePath: vi.fn(),
resolveActiveWorkpieceDir: vi.fn().mockResolvedValue(null),
}));
const { runLeitstandVerify } = await import("../leitstand-commands.ts");
const { readSystemConfigSmart, readSystemStateSmart } =
await import("../../sternsystem/registry-io.ts");
function makeInput(flags: Record) {
return { flags, args: [] } as unknown as Parameters[0];
}
function makeContext(workspaceRoot: string) {
return {
io: createDefaultIO().io,
workspaceRoot,
dryRun: false,
site: undefined,
} as unknown as Parameters[1];
}
const defaultConfig = {
schemaVersion: "1.0.0",
id: "test-site",
deployment: {
adapter: "cloudflare-workers" as const,
channels: {
dev: { workerName: "dev-test", url: "https://dev.test.com" },
alt: { workerName: "alt-test", url: "https://alt.test.com" },
main: { workerName: "test", url: "https://test.com" },
},
},
};
const defaultState = {
schemaVersion: "1.0.0",
systemId: "test-site",
currentMission: null,
lastRelease: null,
lastPropagated: {
dev: {
releaseId: "r000005",
at: "2026-08-23T12:00:00.000Z",
healthy: true,
state: "succeeded" as const,
operationId: "op-1",
leaseExpiresAt: null,
},
alt: {
releaseId: "r000005",
at: "2026-08-23T13:00:00.000Z",
healthy: true,
state: "succeeded" as const,
operationId: "op-2",
leaseExpiresAt: null,
},
main: {
releaseId: "r000005",
at: "2026-08-23T14:00:00.000Z",
healthy: true,
state: "succeeded" as const,
operationId: "op-3",
leaseExpiresAt: null,
},
},
accessPin: null as string | null,
};
function mockBuildIdentity(distTreeHash: string, releaseId = "r000005") {
return JSON.stringify({
distTreeHash,
releaseId,
missionId: "m000076",
buildTimestamp: "2026-08-23T12:45:06Z",
});
}
afterEach(() => {
vi.restoreAllMocks();
});
describe("RFC-0930: leitstand.verify", () => {
it("all-match success: all channels reachable, same hash, match local → exitCode 0", async () => {
vi.mocked(readSystemConfigSmart).mockResolvedValueOnce(defaultConfig as never);
vi.mocked(readSystemStateSmart).mockResolvedValueOnce(defaultState as never);
vi.spyOn(globalThis, "fetch").mockImplementation(() =>
Promise.resolve(new Response(mockBuildIdentity("sha256:abc123"), { status: 200 })),
);
const result = await runLeitstandVerify(makeInput({ site: "test-site" }), makeContext("/tmp"));
expect(result.exitCode).toBe(0);
expect(result.data!.crossChannel.allReachable).toBe(true);
expect(result.data!.crossChannel.allSameHash).toBe(true);
expect(result.data!.crossChannel.commonHash).toBe("sha256:abc123");
expect(result.data!.channels).toHaveLength(3);
for (const ch of result.data!.channels) {
expect(ch.matchesLocal).toBe(true);
expect(ch.reachable).toBe(true);
}
});
it("hash drift: channels return different distTreeHash → exitCode 1", async () => {
vi.mocked(readSystemConfigSmart).mockResolvedValueOnce(defaultConfig as never);
vi.mocked(readSystemStateSmart).mockResolvedValueOnce(defaultState as never);
const hashes = ["sha256:aaa", "sha256:bbb", "sha256:ccc"];
let callIndex = 0;
vi.spyOn(globalThis, "fetch").mockImplementation(() => {
const hash = hashes[callIndex++ % hashes.length];
return Promise.resolve(new Response(mockBuildIdentity(hash), { status: 200 }));
});
const result = await runLeitstandVerify(makeInput({ site: "test-site" }), makeContext("/tmp"));
expect(result.exitCode).toBe(1);
expect(result.data!.crossChannel.allSameHash).toBe(false);
expect(result.data!.crossChannel.commonHash).toBeNull();
});
it("unreachable channel: main returns HTTP 500 → exitCode 1", async () => {
vi.mocked(readSystemConfigSmart).mockResolvedValueOnce(defaultConfig as never);
vi.mocked(readSystemStateSmart).mockResolvedValueOnce(defaultState as never);
vi.spyOn(globalThis, "fetch").mockImplementation((url: string | URL | Request) => {
const urlStr = typeof url === "string" ? url : url.toString();
if (urlStr.includes("test.com/.well-known")) {
return Promise.resolve(new Response("Internal Server Error", { status: 500 }));
}
return Promise.resolve(new Response(mockBuildIdentity("sha256:abc"), { status: 200 }));
});
const result = await runLeitstandVerify(makeInput({ site: "test-site" }), makeContext("/tmp"));
expect(result.exitCode).toBe(1);
expect(result.data!.crossChannel.allReachable).toBe(false);
const mainCh = result.data!.channels.find((c) => c.channel === "main");
expect(mainCh?.reachable).toBe(false);
expect(mainCh?.httpStatus).toBe(500);
});
it("local mismatch: live releaseId differs from system-state.yaml → exitCode 1", async () => {
vi.mocked(readSystemConfigSmart).mockResolvedValueOnce(defaultConfig as never);
vi.mocked(readSystemStateSmart).mockResolvedValueOnce(defaultState as never);
vi.spyOn(globalThis, "fetch").mockImplementation(() =>
Promise.resolve(new Response(mockBuildIdentity("sha256:abc", "r000099"), { status: 200 })),
);
const result = await runLeitstandVerify(makeInput({ site: "test-site" }), makeContext("/tmp"));
expect(result.exitCode).toBe(1);
for (const ch of result.data!.channels) {
expect(ch.matchesLocal).toBe(false);
}
});
it("single-channel filter: --channel dev → only dev in output", async () => {
vi.mocked(readSystemConfigSmart).mockResolvedValueOnce(defaultConfig as never);
vi.mocked(readSystemStateSmart).mockResolvedValueOnce(defaultState as never);
vi.spyOn(globalThis, "fetch").mockImplementation(() =>
Promise.resolve(new Response(mockBuildIdentity("sha256:abc"), { status: 200 })),
);
const result = await runLeitstandVerify(
makeInput({ site: "test-site", channel: "dev" }),
makeContext("/tmp"),
);
expect(result.data!.channels).toHaveLength(1);
expect(result.data!.channels[0].channel).toBe("dev");
});
it("access-protected channel: auth header sent when accessPin is set", async () => {
vi.mocked(readSystemConfigSmart).mockResolvedValueOnce(defaultConfig as never);
vi.mocked(readSystemStateSmart).mockResolvedValueOnce({
...defaultState,
accessPin: "1234",
} as never);
const fetchSpy = vi
.spyOn(globalThis, "fetch")
.mockImplementation(() =>
Promise.resolve(new Response(mockBuildIdentity("sha256:abc"), { status: 200 })),
);
await runLeitstandVerify(makeInput({ site: "test-site" }), makeContext("/tmp"));
expect(fetchSpy).toHaveBeenCalled();
const callArgs = fetchSpy.mock.calls[0];
const options = callArgs?.[1] as RequestInit | undefined;
expect(options?.headers).toHaveProperty("Authorization");
const authHeader = (options?.headers as Record)?.Authorization;
expect(authHeader).toMatch(/^Basic /);
expect(atob(authHeader!.replace("Basic ", ""))).toBe("warp:1234");
});
it("build-identity 404: channel returns 404 → buildIdentity null, error set", async () => {
vi.mocked(readSystemConfigSmart).mockResolvedValueOnce(defaultConfig as never);
vi.mocked(readSystemStateSmart).mockResolvedValueOnce(defaultState as never);
vi.spyOn(globalThis, "fetch").mockImplementation(() =>
Promise.resolve(new Response("Not Found", { status: 404 })),
);
const result = await runLeitstandVerify(makeInput({ site: "test-site" }), makeContext("/tmp"));
expect(result.exitCode).toBe(1);
for (const ch of result.data!.channels) {
expect(ch.buildIdentity).toBeNull();
expect(ch.error).toBe("build-identity.json not found");
}
});
it("no deployment config: returns empty channels, exitCode 0", async () => {
vi.mocked(readSystemConfigSmart).mockResolvedValueOnce({
schemaVersion: "1.0.0",
id: "test-site",
deployment: undefined,
} as never);
vi.mocked(readSystemStateSmart).mockResolvedValueOnce(defaultState as never);
const result = await runLeitstandVerify(makeInput({ site: "test-site" }), makeContext("/tmp"));
expect(result.exitCode).toBe(0);
expect(result.data!.channels).toHaveLength(0);
expect(result.data!.crossChannel.allReachable).toBe(true);
expect(result.data!.crossChannel.allSameHash).toBe(true);
expect(result.summary).toContain("no deployment config");
});
it("--no-compare-local: matchesLocal is null for all channels", async () => {
vi.mocked(readSystemConfigSmart).mockResolvedValueOnce(defaultConfig as never);
vi.mocked(readSystemStateSmart).mockResolvedValueOnce(defaultState as never);
vi.spyOn(globalThis, "fetch").mockImplementation(() =>
Promise.resolve(new Response(mockBuildIdentity("sha256:abc"), { status: 200 })),
);
const result = await runLeitstandVerify(
makeInput({ site: "test-site", "compare-local": false }),
makeContext("/tmp"),
);
for (const ch of result.data!.channels) {
expect(ch.matchesLocal).toBeNull();
}
});
it("fetch timeout: AbortError → reachable false, error mentions timeout", async () => {
vi.mocked(readSystemConfigSmart).mockResolvedValueOnce(defaultConfig as never);
vi.mocked(readSystemStateSmart).mockResolvedValueOnce(defaultState as never);
vi.spyOn(globalThis, "fetch").mockImplementation((_url, init) => {
return new Promise((_resolve, reject) => {
const signal = init?.signal;
if (signal) {
signal.addEventListener("abort", () => {
const err = new Error("Aborted");
err.name = "AbortError";
reject(err);
});
}
});
});
const result = await runLeitstandVerify(
makeInput({ site: "test-site", "timeout-ms": "50" }),
makeContext("/tmp"),
);
expect(result.exitCode).toBe(1);
for (const ch of result.data!.channels) {
expect(ch.reachable).toBe(false);
expect(ch.error).toContain("timeout");
}
});
it("throws if --site is missing", async () => {
await expect(runLeitstandVerify(makeInput({}), makeContext("/tmp"))).rejects.toThrow(
"--site is required",
);
});
it("--system alias works as --site", async () => {
vi.mocked(readSystemConfigSmart).mockResolvedValueOnce(defaultConfig as never);
vi.mocked(readSystemStateSmart).mockResolvedValueOnce(defaultState as never);
vi.spyOn(globalThis, "fetch").mockImplementation(() =>
Promise.resolve(new Response(mockBuildIdentity("sha256:abc"), { status: 200 })),
);
const result = await runLeitstandVerify(
makeInput({ system: "test-site" }),
makeContext("/tmp"),
);
expect(result.data!.systemId).toBe("test-site");
});
});