/*
RFC-1326 acceptance tests for cloudflare.token.check — AC-3 (a token
lacking Vectorize reports account.vectorize.edit as missing), AC-4 (a
missing always-permission exits non-zero), unprobeable entries report
unknown, and zone-scope entries degrade to unknown without zone context.
All API calls are stubbed fetch — no network.
- RFC-1326: initial — AC-3/AC-4 stubbed-fetch cases, unknown/no-zone paths.
- RFC-1326: single Cloudflare permission registry, token preflight, birth provisioning
*/
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import type { KernelCommandInput, KernelRuntimeContext } from "@warpgogol/werkstatt-engine/kernel";
import { CLOUDFLARE_PERMISSIONS } from "@warpgogol/werkstatt-shared/cloudflare/permissions";
import { runCloudflareTokenCheck } from "../cloudflare-token-check.ts";
const ACCOUNT_ID = "a".repeat(32);
const ZONE_ID = "b".repeat(32);
function makeInput(flags: Record = {}): KernelCommandInput {
return { argv: [], flags } as unknown as KernelCommandInput;
}
function makeContext(): KernelRuntimeContext {
return {
workspaceRoot: "/tmp",
logger: { info: () => {}, warn: () => {}, error: () => {}, debug: () => {}, success: () => {} },
} as unknown as KernelRuntimeContext;
}
interface ProbeData {
results: { id: string; outcome: string }[];
missing: string[];
unknown: string[];
tokenId: string;
tokenStatus: string;
}
function apiResponse(body: unknown, status = 200): Response {
return new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json" },
});
}
/**
* Stub fetch: verifyToken succeeds; every registry probe answers ok except
* the ids in `missingIds`, which get a Cloudflare 403 permission error.
*/
function stubFetch(missingIds: string[] = []): void {
const missingPaths = new Set(
CLOUDFLARE_PERMISSIONS.filter((p) => missingIds.includes(p.id)).map((p) =>
p.probe!.path.replace("{accountId}", ACCOUNT_ID).replace("{zoneId}", ZONE_ID),
),
);
vi.stubGlobal(
"fetch",
vi.fn(async (url: string | URL | Request) => {
const u = String(url instanceof Request ? url.url : url);
if (u.includes("/user/tokens/verify")) {
return apiResponse({ success: true, errors: [], messages: [], result: { id: "tok-123", status: "active" } });
}
const path = u.replace("https://api.cloudflare.com/client/v4", "");
if (missingPaths.has(path)) {
return apiResponse(
{ success: false, errors: [{ code: 9109, message: "Permission denied" }], messages: [] },
403,
);
}
return apiResponse({ success: true, errors: [], messages: [], result: [] });
}),
);
}
beforeEach(() => {
process.env.CLOUDFLARE_API_TOKEN = "test-token";
process.env.CLOUDFLARE_ACCOUNT_ID = ACCOUNT_ID;
delete process.env.CLOUDFLARE_ZONE_ID;
});
afterEach(() => {
vi.unstubAllGlobals();
delete process.env.CLOUDFLARE_API_TOKEN;
delete process.env.CLOUDFLARE_ACCOUNT_ID;
delete process.env.CLOUDFLARE_ZONE_ID;
});
describe("cloudflare.token.check (RFC-1326)", () => {
test("AC-3+AC-4: a token lacking Vectorize reports missing and exits non-zero", async () => {
stubFetch(["account.vectorize.edit"]);
const result = await runCloudflareTokenCheck(makeInput({ zone: ZONE_ID }), makeContext());
expect(result.exitCode).toBe(1);
const data = result.data as ProbeData;
const vectorize = data.results.find((r) => r.id === "account.vectorize.edit");
expect(vectorize?.outcome).toBe("missing");
expect(data.missing).toContain("account.vectorize.edit");
expect(data.tokenId).toBe("tok-123");
expect(result.summary).toContain("account.vectorize.edit");
});
test("a fully-granted token passes with exit 0", async () => {
stubFetch([]);
const result = await runCloudflareTokenCheck(makeInput({ zone: ZONE_ID }), makeContext());
expect(result.exitCode).toBe(0);
const data = result.data as ProbeData;
expect(data.missing).toEqual([]);
expect(data.results.find((r) => r.id === "zone.cache-purge.purge")?.outcome).toBe("unknown");
});
test("a missing custom-domain permission warns but does not fail", async () => {
stubFetch(["zone.dns.edit"]);
const result = await runCloudflareTokenCheck(makeInput({ zone: ZONE_ID }), makeContext());
expect(result.exitCode).toBe(0);
const data = result.data as ProbeData;
expect(data.missing).toContain("zone.dns.edit");
});
test("no zone context → every zone-scope entry is unknown, never missing", async () => {
stubFetch(["zone.dns.edit", "zone.zone.read"]);
const result = await runCloudflareTokenCheck(makeInput(), makeContext());
expect(result.exitCode).toBe(0);
const data = result.data as ProbeData;
for (const r of data.results.filter((r) => r.id.startsWith("zone."))) {
expect(r.outcome, r.id).toBe("unknown");
}
expect(data.missing).toEqual([]);
});
test("unprobeable permission reports unknown even when probes would 403", async () => {
stubFetch([]);
const result = await runCloudflareTokenCheck(makeInput({ zone: ZONE_ID }), makeContext());
const data = result.data as ProbeData;
expect(data.results.find((r) => r.id === "zone.cache-purge.purge")?.outcome).toBe("unknown");
expect(data.unknown).toContain("zone.cache-purge.purge");
});
test("missing CLOUDFLARE_API_TOKEN fails fast without calling the API", async () => {
delete process.env.CLOUDFLARE_API_TOKEN;
const fetchSpy = vi.fn();
vi.stubGlobal("fetch", fetchSpy);
const result = await runCloudflareTokenCheck(makeInput(), makeContext());
expect(result.exitCode).toBe(1);
expect(fetchSpy).not.toHaveBeenCalled();
});
test("inactive token fails with token-inactive", async () => {
vi.stubGlobal(
"fetch",
vi.fn(async () =>
apiResponse({ success: true, errors: [], messages: [], result: { id: "tok-9", status: "disabled" } }),
),
);
const result = await runCloudflareTokenCheck(makeInput(), makeContext());
expect(result.exitCode).toBe(1);
expect(result.summary).toContain("token-inactive");
});
});