/*
RFC-1326 acceptance tests for loadWorkshopEnvAllowlisted — AC-6 (a kernel
key absent from the process environment is read from the workshop .env),
AC-7 (a non-allow-listed key in the workshop .env never reaches
process.env — the LANGUAGETOOL_URL fixture), exported-var-wins precedence,
and missing-.env tolerance. All tests inject targetEnv — process.env is
never mutated.
- RFC-1326: initial — AC-6/AC-7 cases + precedence/tolerance invariants.
- RFC-1326: single Cloudflare permission registry, token preflight, birth provisioning
- RFC-1326: review-fix pass — siteName targeting for app-scoped provisioning, allow-list completion
*/
import { afterEach, describe, expect, test } from "vitest";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { createDefaultIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io";
import { loadWorkshopEnvAllowlisted } from "./workshop-env.ts";
let roots: string[] = [];
afterEach(async () => {
for (const root of roots) await rm(root, { recursive: true, force: true });
roots = [];
});
async function fixture(envContent?: string): Promise {
const root = await mkdtemp(join(tmpdir(), "workshop-env-"));
roots.push(root);
if (envContent !== undefined) {
await writeFile(join(root, ".env"), envContent);
}
return root;
}
describe("loadWorkshopEnvAllowlisted (RFC-1326)", () => {
test("AC-6: an allow-listed key absent from the environment loads from .env", async () => {
const root = await fixture("CLOUDFLARE_API_TOKEN=cf-token-123\nCLOUDFLARE_ACCOUNT_ID=acc-1\n");
const target: Record = {};
const result = await loadWorkshopEnvAllowlisted(root, createDefaultIO().io, target);
expect(result.envFileFound).toBe(true);
expect(target.CLOUDFLARE_API_TOKEN).toBe("cf-token-123");
expect(target.CLOUDFLARE_ACCOUNT_ID).toBe("acc-1");
expect(result.loaded.sort()).toEqual(["CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_API_TOKEN"]);
});
test("AC-7: LANGUAGETOOL_URL and other site/service keys never reach the environment", async () => {
const root = await fixture(
[
"CLOUDFLARE_API_TOKEN=cf-token-123",
"LANGUAGETOOL_URL=http://localhost:8010",
"LAGEBILD_API_KEY=lb-key",
"GH_TOKEN=gh-secret",
"OPENAI_API_KEY=sk-test",
"UPSTASH_REDIS_REST_URL=https://upstash.example",
"SEARCH_REINDEX_TOKEN=reindex-secret",
"STRIPE_SECRET_KEY=sk_live_x",
"FLEET_OWNERSHIP_REGISTRY_URL=https://registry.example",
"R2_AXIOM_ACCOUNT_ID=r2-acc",
].join("\n"),
);
const target: Record = {};
const result = await loadWorkshopEnvAllowlisted(root, createDefaultIO().io, target);
expect(target.CLOUDFLARE_API_TOKEN).toBe("cf-token-123");
expect(target.R2_AXIOM_ACCOUNT_ID).toBe("r2-acc");
// RFC-1326: workspace commands read FLEET_OWNERSHIP_REGISTRY_* and
// UPSTASH_REDIS_REST_* (agent.registration.* ops) — both are allow-listed,
// unlike site/service secrets.
expect(target.FLEET_OWNERSHIP_REGISTRY_URL).toBe("https://registry.example");
expect(target.UPSTASH_REDIS_REST_URL).toBe("https://upstash.example");
for (const denied of [
"LANGUAGETOOL_URL",
"LAGEBILD_API_KEY",
"GH_TOKEN",
"OPENAI_API_KEY",
"SEARCH_REINDEX_TOKEN",
"STRIPE_SECRET_KEY",
]) {
expect(target[denied], denied).toBeUndefined();
}
expect(result.loaded.sort()).toEqual([
"CLOUDFLARE_API_TOKEN",
"FLEET_OWNERSHIP_REGISTRY_URL",
"R2_AXIOM_ACCOUNT_ID",
"UPSTASH_REDIS_REST_URL",
]);
});
test("an exported variable always wins — .env never overrides process env", async () => {
const root = await fixture("CLOUDFLARE_API_TOKEN=dotenv-value\n");
const target: Record = {
CLOUDFLARE_API_TOKEN: "exported-value",
};
const result = await loadWorkshopEnvAllowlisted(root, createDefaultIO().io, target);
expect(target.CLOUDFLARE_API_TOKEN).toBe("exported-value");
expect(result.loaded).toEqual([]);
expect(result.skipped).toContain("CLOUDFLARE_API_TOKEN");
});
test("missing .env is tolerated — no keys loaded, no throw", async () => {
const root = await fixture();
const target: Record = {};
const result = await loadWorkshopEnvAllowlisted(root, createDefaultIO().io, target);
expect(result.envFileFound).toBe(false);
expect(result.loaded).toEqual([]);
});
});