/*
Unit tests for lagebild commands covering RFC-1065: lagebild.connect; RFC-1065: lagebild.validate; RFC-1065: lagebild.status.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- RFC-1065: initial lagebild command tests.
- warpgogol-m000170: EC-16 — isolate workspace root in a per-run tmpdir sandbox so
resolveCacheClonePath resolves to a private systems-cache pool; un-quarantine
from .flaky-tests.json.
- RFC-1198: cookie-free referral attribution — /r/ partner landings + voluntary referrer field
Replace the RFC-1191 wg_ref cookie capture with URL+form attribution:
static /r/ and //r/ partner landings (noindex, zero JS,
consent-gated via publicNameConsentRef), ?ref prefill on the existing
referrerField rail, server-side slug resolution against the bundled
partners.yaml, referral_partner_slug/name on the Lagebild ingress contract,
legacy /?ref -> /r/ 302 in the generated worker, and full removal of
the cookie capture module (wg_ref, referral.attribution entitlement,
payload.attribution).
*/
import { describe, expect, it, vi, beforeEach, afterAll } from "vitest";
import { mkdtempSync, rmSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { createDefaultIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io";
// Mock registry-io
vi.mock("../sternsystem/registry-io.ts", () => ({
readSystemConfigSmart: vi.fn(),
readSystemStateSmart: vi.fn(),
writeSystemStateSmart: vi.fn(),
resolveCacheClonePath: vi.fn((root: string, id: string) => `${root}/../systems-cache/${id}`),
}));
// Mock wrangler-secrets
vi.mock("../leitstand/adapters/wrangler-secrets.ts", () => ({
runWranglerSecretPut: vi.fn(),
runWranglerSecretDelete: vi.fn(),
}));
// Mock adapters
vi.mock("../leitstand/adapters/index.ts", () => ({
sourceDotenv: vi.fn(async () => ({})),
filterEnv: vi.fn((env: Record) => env),
}));
// Mock node:fs and node:fs/promises for .env read/write.
// Spread-actual: the WorkspaceIO default adapter needs the rest of the real
// fs surface (rename, mkdir, stat, …) — full replacement leaves them undefined.
vi.mock("node:fs", async (importOriginal) => ({
...(await importOriginal()),
existsSync: vi.fn(() => false),
}));
const { mockReadFile, mockWriteFile, mockSpawn } = vi.hoisted(() => ({
mockReadFile: vi.fn(),
mockWriteFile: vi.fn(),
mockSpawn: vi.fn(),
}));
vi.mock("node:fs/promises", async (importOriginal) => ({
...(await importOriginal()),
readFile: mockReadFile,
writeFile: mockWriteFile,
}));
// Mock node:child_process for wrangler secret list (used by validate handler)
vi.mock("node:child_process", () => ({
spawn: mockSpawn,
}));
// Mock global fetch
const mockFetch = vi.fn();
global.fetch = mockFetch as unknown as typeof fetch;
const { runLagebildConnect } = await import("./lagebild-connect.ts");
const { runLagebildValidate } = await import("./lagebild-validate.ts");
const { runLagebildStatus } = await import("./lagebild-status.ts");
const { runLagebildReferralIssue, runLagebildReferralList } =
await import("./lagebild-referral.ts");
const { sourceDotenv } = await import("../leitstand/adapters/index.ts");
const { readSystemConfigSmart, readSystemStateSmart, writeSystemStateSmart } =
await import("../sternsystem/registry-io.ts");
const { runWranglerSecretPut } = await import("../leitstand/adapters/wrangler-secrets.ts");
const { existsSync } = await import("node:fs");
function mockSpawnChild(stdout: string, stderr: string = "", exitCode: number = 0) {
const dataCbs: ((d: Buffer) => void)[] = [];
const stderrCbs: ((d: Buffer) => void)[] = [];
const exitCbs: ((code: number | null) => void)[] = [];
const mockChild = {
stdout: {
on: (_event: string, cb: (d: Buffer) => void) => {
if (_event === "data") dataCbs.push(cb);
},
},
stderr: {
on: (_event: string, cb: (d: Buffer) => void) => {
if (_event === "data") stderrCbs.push(cb);
},
},
on: (event: string, cb: (...args: unknown[]) => void) => {
if (event === "exit" || event === "close") exitCbs.push(cb as (code: number | null) => void);
},
stdin: {
write: () => {},
end: () => {},
},
kill: () => {},
};
// execImpl listens on "close" and never calls stdin.end — the fire must be
// scheduled when spawn() is invoked (not at mockSpawnChild setup): fs awaits
// between setup and io.exec would burn the event-loop turn and fire with
// empty handler arrays. Handlers attach synchronously after spawn returns.
mockSpawn.mockImplementationOnce(() => {
setImmediate(() => {
for (const cb of dataCbs) cb(Buffer.from(stdout));
if (stderr) for (const cb of stderrCbs) cb(Buffer.from(stderr));
for (const cb of exitCbs) cb(exitCode);
});
return mockChild;
});
}
function makeInput(flags: Record) {
return { flags, argv: [] } as unknown as Parameters[0];
}
// EC-16: the workspace root feeds `resolveCacheClonePath` → "/../systems-cache/".
// A shared literal like /tmp/test makes sibling-pool test files race on
// /tmp/systems-cache (ENOENT flakes under `vitest related`). Per-run sandbox keeps
// this file isolated; teardown removes it.
const workRoot = mkdtempSync(join(tmpdir(), "lagebild-commands-"));
afterAll(() => {
rmSync(workRoot, { recursive: true, force: true });
});
function makeContext() {
return {
io: createDefaultIO().io,
workspaceRoot: workRoot,
dryRun: false,
site: undefined,
} as unknown as Parameters[1];
}
const mockConfig = {
schemaVersion: "1.0.0",
id: "test-site",
cosmicStar: "Vega",
mirrors: [{ path: "../systems-cache/test-site", storageType: "non-bare" as const }],
pinnedPlatform: "6.276.132",
status: "active" as const,
registeredAt: "2026-01-01T00:00:00Z",
deployment: {
adapter: "cloudflare-workers" as const,
channels: {
dev: { workerName: "test-site-dev", url: "https://dev.test-site.example" },
alt: { workerName: "test-site-alt", url: "https://alt.test-site.example" },
main: { workerName: "test-site-main", url: "https://test-site.example" },
},
},
};
const mockState = {
schemaVersion: "1.0.0",
systemId: "test-site",
currentMission: null,
lastRelease: null,
lastPropagated: {},
accessPin: null,
passportRequired: false,
ownershipRequired: false,
};
beforeEach(async () => {
vi.mocked(readSystemConfigSmart).mockReset();
vi.mocked(readSystemStateSmart).mockReset();
vi.mocked(writeSystemStateSmart).mockReset();
vi.mocked(runWranglerSecretPut).mockReset();
vi.mocked(existsSync).mockReset();
mockReadFile.mockReset();
mockWriteFile.mockReset();
mockFetch.mockReset();
mockSpawn.mockReset();
// io.writeFile goes through writeFileAtomic (temp write + fsRename) — a bare
// vi.fn() no-ops the temp write and rename fails ENOENT. Route the mocks to
// the real fs; per-test mockResolvedValue can still override.
const realFsPromises =
await vi.importActual("node:fs/promises");
mockReadFile.mockImplementation(realFsPromises.readFile as never);
mockWriteFile.mockImplementation(realFsPromises.writeFile as never);
vi.mocked(readSystemConfigSmart).mockResolvedValue(structuredClone(mockConfig) as never);
vi.mocked(readSystemStateSmart).mockResolvedValue(structuredClone(mockState) as never);
vi.mocked(writeSystemStateSmart).mockResolvedValue(undefined as never);
vi.mocked(runWranglerSecretPut).mockResolvedValue({
exitCode: 0,
stdout: "",
stderr: "",
} as never);
mockFetch.mockResolvedValue({ ok: true, status: 200 } as never);
vi.mocked(existsSync).mockReturnValue(false);
mockReadFile.mockResolvedValue("");
});
describe("RFC-1065: lagebild.connect", () => {
it("AC-1: pushes 4 LAGEBILD_* secrets via runWranglerSecretPut", async () => {
await runLagebildConnect(
makeInput({
site: "test-site",
"api-url": "https://api.lagebild.example",
"api-key": "secret-key-123",
"tenant-id": "tenant-1",
"source-system-id": "src-1",
channel: "main",
}),
makeContext(),
);
expect(runWranglerSecretPut).toHaveBeenCalledTimes(4);
const calls = vi.mocked(runWranglerSecretPut).mock.calls;
const secretNames = calls.map((c) => c[1]);
expect(secretNames).toContain("LAGEBILD_API_URL");
expect(secretNames).toContain("LAGEBILD_API_KEY");
expect(secretNames).toContain("LAGEBILD_TENANT_ID");
expect(secretNames).toContain("LAGEBILD_SOURCE_SYSTEM_ID");
});
it("AC-2: returns per-secret pushed: boolean", async () => {
vi.mocked(runWranglerSecretPut)
.mockResolvedValueOnce({ exitCode: 0, stdout: "", stderr: "" })
.mockResolvedValueOnce({ exitCode: 1, stdout: "", stderr: "error" })
.mockResolvedValueOnce({ exitCode: 0, stdout: "", stderr: "" })
.mockResolvedValueOnce({ exitCode: 0, stdout: "", stderr: "" });
const result = await runLagebildConnect(
makeInput({
site: "test-site",
"api-url": "https://api.lagebild.example",
"api-key": "secret-key-123",
"tenant-id": "tenant-1",
}),
makeContext(),
);
expect(result.data!.secrets).toHaveLength(4);
const pushedFlags = result.data!.secrets.map((s) => s.pushed);
expect(pushedFlags).toContain(false);
expect(pushedFlags).toContain(true);
});
it("AC-3: resolves workerName from system-config.yaml for specified channel", async () => {
await runLagebildConnect(
makeInput({
site: "test-site",
"api-url": "https://api.lagebild.example",
"api-key": "secret-key-123",
"tenant-id": "tenant-1",
channel: "dev",
}),
makeContext(),
);
expect(runWranglerSecretPut).toHaveBeenCalled();
const firstCall = vi.mocked(runWranglerSecretPut).mock.calls[0];
expect(firstCall[0]).toBe("test-site-dev");
});
it("AC-4: rejects empty --api-key", async () => {
await expect(
runLagebildConnect(
makeInput({
site: "test-site",
"api-url": "https://api.lagebild.example",
"api-key": "",
"tenant-id": "tenant-1",
}),
makeContext(),
),
).rejects.toThrow("--api-key");
});
it("AC-12: writes lagebild block to system-state.yaml", async () => {
await runLagebildConnect(
makeInput({
site: "test-site",
"api-url": "https://api.lagebild.example",
"api-key": "secret-key-123",
"tenant-id": "tenant-1",
"source-system-id": "src-1",
channel: "main",
}),
makeContext(),
);
expect(writeSystemStateSmart).toHaveBeenCalledTimes(1);
const writtenState = vi.mocked(writeSystemStateSmart).mock.calls[0][2] as typeof mockState & {
lagebild?: { connected: boolean; apiUrl: string; tenantId: string };
};
expect(writtenState.lagebild).toBeDefined();
expect(writtenState.lagebild!.connected).toBe(true);
expect(writtenState.lagebild!.apiUrl).toBe("https://api.lagebild.example");
expect(writtenState.lagebild!.tenantId).toBe("tenant-1");
});
it("AC-11: throws 'Site not found' when system-config.yaml is missing", async () => {
const enoentError = new Error("ENOENT: no such file or directory");
vi.mocked(readSystemConfigSmart).mockRejectedValueOnce(enoentError as never);
await expect(
runLagebildConnect(
makeInput({
site: "missing-site",
"api-url": "https://api.lagebild.example",
"api-key": "secret-key-123",
"tenant-id": "tenant-1",
}),
makeContext(),
),
).rejects.toThrow("not found");
});
it("throws if --site is missing", async () => {
await expect(
runLagebildConnect(
makeInput({ "api-url": "u", "api-key": "k", "tenant-id": "t" }),
makeContext(),
),
).rejects.toThrow("--site is required");
});
it("throws if --api-url is missing", async () => {
await expect(
runLagebildConnect(makeInput({ site: "s", "api-key": "k", "tenant-id": "t" }), makeContext()),
).rejects.toThrow("--api-url is required");
});
it("throws if --tenant-id is missing", async () => {
await expect(
runLagebildConnect(makeInput({ site: "s", "api-url": "u", "api-key": "k" }), makeContext()),
).rejects.toThrow("--tenant-id is required");
});
it("defaults source-system-id to site_ when not provided", async () => {
await runLagebildConnect(
makeInput({
site: "test-site",
"api-url": "https://api.lagebild.example",
"api-key": "secret-key-123",
"tenant-id": "tenant-1",
}),
makeContext(),
);
const writtenState = vi.mocked(writeSystemStateSmart).mock.calls[0][2] as typeof mockState & {
lagebild?: { sourceSystemId: string };
};
expect(writtenState.lagebild!.sourceSystemId).toBe("site_test-site");
});
});
describe("RFC-1065: lagebild.validate", () => {
it("AC-5: checks 4 LAGEBILD_* secrets via wrangler secret list", async () => {
mockSpawnChild(
"LAGEBILD_API_URL 2024-01-01T00:00:00Z\nLAGEBILD_API_KEY 2024-01-01T00:00:00Z\nLAGEBILD_TENANT_ID 2024-01-01T00:00:00Z\nLAGEBILD_SOURCE_SYSTEM_ID 2024-01-01T00:00:00Z\n",
);
const result = await runLagebildValidate(
makeInput({ site: "test-site", channel: "main" }),
makeContext(),
);
expect(result.data!.secrets).toHaveLength(4);
expect(result.data!.secrets.map((s) => s.name)).toEqual([
"LAGEBILD_API_URL",
"LAGEBILD_API_KEY",
"LAGEBILD_TENANT_ID",
"LAGEBILD_SOURCE_SYSTEM_ID",
]);
});
it("AC-6: returns allSecretsPresent: true when all 4 secrets present", async () => {
mockSpawnChild(
"LAGEBILD_API_URL 2024-01-01T00:00:00Z\nLAGEBILD_API_KEY 2024-01-01T00:00:00Z\nLAGEBILD_TENANT_ID 2024-01-01T00:00:00Z\nLAGEBILD_SOURCE_SYSTEM_ID 2024-01-01T00:00:00Z\n",
);
const result = await runLagebildValidate(makeInput({ site: "test-site" }), makeContext());
expect(result.data!.allSecretsPresent).toBe(true);
});
it("AC-6: returns allSecretsPresent: false and exitCode 1 when secrets missing", async () => {
mockSpawnChild("LAGEBILD_API_URL 2024-01-01T00:00:00Z\n");
const result = await runLagebildValidate(makeInput({ site: "test-site" }), makeContext());
expect(result.data!.allSecretsPresent).toBe(false);
expect(result.exitCode).toBe(1);
});
it("AC-11: throws 'Site not found' when system-config.yaml is missing", async () => {
const enoentError = new Error("ENOENT: no such file or directory");
vi.mocked(readSystemConfigSmart).mockRejectedValueOnce(enoentError as never);
await expect(
runLagebildValidate(makeInput({ site: "missing-site" }), makeContext()),
).rejects.toThrow("not found");
});
it("throws if --site is missing", async () => {
await expect(runLagebildValidate(makeInput({}), makeContext())).rejects.toThrow(
"--site is required",
);
});
it("returns state.connected from system-state.yaml", async () => {
mockSpawnChild(
"LAGEBILD_API_URL 2024-01-01T00:00:00Z\nLAGEBILD_API_KEY 2024-01-01T00:00:00Z\nLAGEBILD_TENANT_ID 2024-01-01T00:00:00Z\nLAGEBILD_SOURCE_SYSTEM_ID 2024-01-01T00:00:00Z\n",
);
vi.mocked(readSystemStateSmart).mockResolvedValueOnce({
...mockState,
lagebild: {
connected: true,
apiUrl: "https://api.lagebild.example",
tenantId: "tenant-1",
sourceSystemId: "src-1",
connectedAt: "2026-01-01T00:00:00Z",
channel: "main",
},
} as never);
const result = await runLagebildValidate(makeInput({ site: "test-site" }), makeContext());
expect(result.data!.state.connected).toBe(true);
});
});
describe("RFC-1065: lagebild.status", () => {
it("AC-7: reads system-state.yaml via readSystemStateSmart", async () => {
vi.mocked(readSystemStateSmart).mockResolvedValueOnce({
...mockState,
lagebild: {
connected: true,
apiUrl: "https://api.lagebild.example",
tenantId: "tenant-1",
sourceSystemId: "src-1",
connectedAt: "2026-01-01T00:00:00Z",
channel: "main",
},
} as never);
const result = await runLagebildStatus(makeInput({ site: "test-site" }), makeContext());
expect(readSystemStateSmart).toHaveBeenCalledWith(workRoot, "test-site");
expect(result.data!.connected).toBe(true);
expect(result.data!.apiUrl).toBe("https://api.lagebild.example");
expect(result.data!.tenantId).toBe("tenant-1");
expect(result.data!.channel).toBe("main");
});
it("AC-8: returns connected: false when lagebild block is undefined", async () => {
const result = await runLagebildStatus(makeInput({ site: "test-site" }), makeContext());
expect(result.data!.connected).toBe(false);
});
it("AC-9: does not make network calls", async () => {
vi.mocked(readSystemStateSmart).mockResolvedValueOnce(mockState as never);
await runLagebildStatus(makeInput({ site: "test-site" }), makeContext());
expect(mockFetch).not.toHaveBeenCalled();
});
it("throws if --site is missing", async () => {
await expect(runLagebildStatus(makeInput({}), makeContext())).rejects.toThrow(
"--site is required",
);
});
});
const connectedLagebildState = {
...mockState,
lagebild: {
connected: true,
apiUrl: "https://api.lagebild.example",
tenantId: "tenant-1",
sourceSystemId: "src-1",
connectedAt: "2026-01-01T00:00:00Z",
channel: "main",
},
};
describe("RFC-1198: lagebild.referral.issue", () => {
beforeEach(() => {
vi.mocked(readSystemStateSmart).mockResolvedValue(
structuredClone(connectedLagebildState) as never,
);
vi.mocked(sourceDotenv).mockResolvedValue({ LAGEBILD_API_KEY: "key-1" } as never);
mockFetch.mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ slug: "anna" }),
} as never);
});
const issue = (flags: Record) =>
runLagebildReferralIssue(makeInput(flags), makeContext());
it("AC-8: POSTs /v1/referrals and returns {slug, url} with the channel origin", async () => {
const result = await issue({
site: "test-site",
slug: "anna",
name: "Anna Beispiel",
contact: "rec@example.test",
});
expect(mockFetch).toHaveBeenCalledTimes(1);
const [url, init] = mockFetch.mock.calls[0] as [string, RequestInit];
expect(url).toBe("https://api.lagebild.example/v1/referrals");
expect(init.method).toBe("POST");
expect(JSON.parse(init.body as string)).toEqual({
slug: "anna",
display_name: "Anna Beispiel",
contact_email: "rec@example.test",
});
expect(result.data!.slug).toBe("anna");
expect(result.data!.url).toBe("https://test-site.example/r/anna");
});
it("uses the requested channel origin for the /r/ URL", async () => {
const result = await issue({
site: "test-site",
slug: "anna",
name: "Anna Beispiel",
channel: "alt",
});
expect(result.data!.url).toBe("https://alt.test-site.example/r/anna");
});
it("omits contact_email when --contact is absent", async () => {
await issue({ site: "test-site", slug: "anna", name: "Anna Beispiel" });
const [, init] = mockFetch.mock.calls[0] as [string, RequestInit];
expect(JSON.parse(init.body as string)).toEqual({
slug: "anna",
display_name: "Anna Beispiel",
});
});
it("throws when the site is not connected to Lagebild", async () => {
vi.mocked(readSystemStateSmart).mockResolvedValueOnce(structuredClone(mockState) as never);
await expect(issue({ site: "test-site", slug: "anna", name: "Anna" })).rejects.toThrow(
"lagebild.connect",
);
});
it("throws if --site is missing", async () => {
await expect(issue({ slug: "anna", name: "Anna" })).rejects.toThrow("--site is required");
});
it("throws if --slug is missing or malformed", async () => {
await expect(issue({ site: "test-site", name: "Anna" })).rejects.toThrow("--slug");
await expect(issue({ site: "test-site", slug: "Anna!", name: "Anna" })).rejects.toThrow(
"--slug",
);
});
it("throws if --name is missing", async () => {
await expect(issue({ site: "test-site", slug: "anna" })).rejects.toThrow("--name");
});
});
describe("RFC-1198: lagebild.referral.list", () => {
beforeEach(() => {
vi.mocked(readSystemStateSmart).mockResolvedValue(
structuredClone(connectedLagebildState) as never,
);
vi.mocked(sourceDotenv).mockResolvedValue({ LAGEBILD_API_KEY: "key-1" } as never);
mockFetch.mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ rows: [{ slug: "anna", display_name: "Anna Beispiel" }] }),
} as never);
});
it("AC-9: issues an HTTP request and returns the registry rows", async () => {
const result = await runLagebildReferralList(makeInput({ site: "test-site" }), makeContext());
const [url, init] = mockFetch.mock.calls[0] as [string, RequestInit];
expect(url).toBe("https://api.lagebild.example/v1/referrals");
expect(init.method).toBe("GET");
expect(result.data!.rows).toHaveLength(1);
});
});