import { test, expect, describe, beforeEach, afterEach, vi } from "vitest";
import fs from "node:fs/promises";
import path from "node:path";
import os from "node:os";
import { createDefaultIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io";
/*
RFC-1138 AC-4: leitstand.promote invokes sternsystem.sync after a
successful promote when mirrors[2+] are configured — non-fatal, with
mirror.synced reported in the result data.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- RFC-1311: Compass header sweep — declares the leitstand suite's contract for test-file scaffold coverage.
*/
const executeKernelCommandMock = vi.fn<(opts: { commandName: string }) => Promise>();
vi.mock("@warpgogol/werkstatt-engine/kernel", () => ({
executeKernelCommand: (opts: { commandName: string }) => executeKernelCommandMock(opts),
KERNEL_UNIVERSAL_FLAGS: [],
}));
vi.mock("../deploy-helpers.ts", () => ({
authorizeAndDeploy: vi.fn(),
verifyDurableSync: vi.fn(async () => true),
authorizeMainPromotion: vi.fn(async () => ({
authorization: {
ok: true,
outcome: { decisionId: "dec-1", ruleId: "ok", message: "ok" },
},
mainVerification: { ok: true, decisionId: "mv-1" },
})),
buildEffectRecord: vi.fn(() => ({})),
writeDeploymentEffectRecord: vi.fn(async () => {}),
makeR2ConfigFromEnv: vi.fn(() => undefined),
resolveArtifactHash: vi.fn(async () => ({ hash: "abc123", source: "flag", path: undefined })),
resolveGateDecisionPath: vi.fn(
(_dir: string, _rel: string, _gate: string, flag?: string) => flag ?? "/tmp/gate.json",
),
flagSite: vi.fn((input: { flags: Record }) => input.flags["site"] as string),
}));
const executeDeployPhasesMock = vi.fn();
vi.mock("../deploy-execution.ts", () => ({
executeDeployPhases: (ctx: unknown, channel: string) => executeDeployPhasesMock(ctx, channel),
buildAuthHeader: vi.fn(() => ({})),
}));
const readSystemConfigMock = vi.fn();
const channel = { workerName: "acme", url: "https://acme.example" };
vi.mock("../../sternsystem/registry-io.ts", () => ({
readSystemConfigSmart: vi.fn(async () => ({
deployment: {
adapter: "null",
channels: { dev: channel, alt: channel, main: channel },
},
})),
// RFC-1190: currentMission supplies the mission context promote now
// requires — without it the command blocks with MISSION_CONTEXT_UNRESOLVED.
readSystemStateSmart: vi.fn(async () => ({ currentMission: "acme-m000001" })),
resolveCacheClonePath: vi.fn((root: string, id: string) => path.join(root, "systems-cache", id)),
readSystemConfig: () => readSystemConfigMock(),
resolveMirrorPath: vi.fn((root: string, p: string) => path.join(root, p)),
}));
vi.mock("../../customdomain/customdomain-register.ts", () => ({
runCustomdomainRegister: vi.fn(async () => ({ exitCode: 0 })),
}));
vi.mock("../../customdomain/redirect-register.ts", () => ({
runRedirectRegister: vi.fn(async () => ({ exitCode: 0 })),
}));
vi.mock("../adapters/index.ts", async (importOriginal) => ({
...(await importOriginal()),
createCloudflareWorkersAdapter: vi.fn(() => ({ name: "cloudflare" })),
createGitHubPagesAdapter: vi.fn(() => ({ name: "gh-pages" })),
readBehaviorSnapshot: vi.fn(async () => null),
sourceDotenv: vi.fn(async () => ({})),
filterEnv: vi.fn((e: Record) => e),
}));
vi.mock("../cache-purge.ts", () => ({
collectPurgeUrls: vi.fn(() => []),
purgeCacheByUrls: vi.fn(async () => ({})),
skippedPurgeResult: vi.fn(() => ({})),
verifyCloudflareToken: vi.fn(async () => true),
BUILD_IDENTITY_PATH: ".well-known/build-identity.json",
}));
vi.mock("../../artifact-store/index.ts", () => ({
artifactStorePreflight: vi.fn(async () => {}),
}));
vi.mock("../../integrity/signing.ts", () => ({
verifyBuildIdentitySignature: vi.fn(async () => ({ ok: true })),
}));
const { runLeitstandPromote } = await import("../leitstand-commands.ts");
function makeContext(workspaceRoot: string) {
return {
io: createDefaultIO().io,
workspaceRoot,
logger: {
info: () => {},
success: () => {},
warn: () => {},
error: () => {},
debug: () => {},
},
} as never;
}
function makeInput(systemId: string, releaseId: string) {
return {
flags: { site: systemId, release: releaseId, "artifact-hash": "abc123" },
argv: [],
} as never;
}
describe("leitstand.promote — RFC-1138 post-promote mirror sync", () => {
let tmpDir: string;
beforeEach(async () => {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "promote-"));
await fs.mkdir(path.join(tmpDir, "systems-cache", "acme", "gate-decisions"), {
recursive: true,
});
executeKernelCommandMock.mockReset();
executeDeployPhasesMock.mockReset();
readSystemConfigMock.mockReset();
executeDeployPhasesMock.mockResolvedValue({
deploymentUrl: "https://acme.example",
failingPhase: undefined,
freshness: { verified: true },
evidenceSynced: true,
purgeResult: undefined,
healthState: "healthy",
});
});
afterEach(async () => {
await fs.rm(tmpDir, { recursive: true, force: true });
});
test("AC-4: successful promote with mirrors[2+] invokes sternsystem.sync", async () => {
readSystemConfigMock.mockResolvedValue({
mirrors: [
{ path: "../cache" },
{ path: "../bare.git" },
{ path: "https://github.com/example/acme.git" },
],
});
executeKernelCommandMock.mockResolvedValue({ exitCode: 0, summary: "synced" });
const result = (await runLeitstandPromote(
makeInput("acme", "acme-r000001"),
makeContext(tmpDir),
)) as { data: { mirror?: { synced: boolean; syncError: string | null } }; exitCode: number };
expect(result.exitCode).toBe(0);
const syncCall = executeKernelCommandMock.mock.calls.find(
(c) => c[0].commandName === "sternsystem.sync",
);
expect(syncCall).toBeDefined();
expect(syncCall![0]).toMatchObject({ argv: ["--id=acme"] });
expect(result.data.mirror).toEqual({ synced: true, syncError: null });
});
test("AC-4: sync failure is non-fatal — promote still succeeds, mirror.synced=false", async () => {
readSystemConfigMock.mockResolvedValue({
mirrors: [
{ path: "../cache" },
{ path: "../bare.git" },
{ path: "https://github.com/example/acme.git" },
],
});
executeKernelCommandMock.mockResolvedValue({ exitCode: 1, summary: "push rejected" });
const result = (await runLeitstandPromote(
makeInput("acme", "acme-r000001"),
makeContext(tmpDir),
)) as { data: { mirror?: { synced: boolean; syncError: string | null } }; exitCode: number };
expect(result.exitCode).toBe(0);
expect(result.data.mirror).toEqual({ synced: false, syncError: "push rejected" });
});
test("AC-4: no external mirrors → no sync call, mirror undefined", async () => {
readSystemConfigMock.mockResolvedValue({
mirrors: [{ path: "../cache" }, { path: "../bare.git" }],
});
const result = (await runLeitstandPromote(
makeInput("acme", "acme-r000001"),
makeContext(tmpDir),
)) as { data: { mirror?: unknown }; exitCode: number };
expect(result.exitCode).toBe(0);
const syncCall = executeKernelCommandMock.mock.calls.find(
(c) => c[0].commandName === "sternsystem.sync",
);
expect(syncCall).toBeUndefined();
expect(result.data.mirror).toBeUndefined();
});
test("AC-4: failed promote skips the sync call", async () => {
readSystemConfigMock.mockResolvedValue({
mirrors: [
{ path: "../cache" },
{ path: "../bare.git" },
{ path: "https://github.com/example/acme.git" },
],
});
executeDeployPhasesMock.mockResolvedValue({
deploymentUrl: "",
failingPhase: "wrangler-deploy",
freshness: { verified: false },
evidenceSynced: false,
healthState: "unknown",
});
const result = (await runLeitstandPromote(
makeInput("acme", "acme-r000001"),
makeContext(tmpDir),
)) as { exitCode: number };
expect(result.exitCode).toBe(1);
const syncCall = executeKernelCommandMock.mock.calls.find(
(c) => c[0].commandName === "sternsystem.sync",
);
expect(syncCall).toBeUndefined();
});
});