clone

a deno framework for local-first web apps that open instantly, work offline and sync when users choose

feat/fix-sync-enrollment-startup
lofi / tools / env_contract_test.ts
env_contract_test.ts 3.2K
1import { validateEnvironment } from "./env_contract.ts";2import { loadEnvironment, mergeEnvironment, parseDotenv } from "./load_env.ts";34function assert(condition: unknown, message: string): asserts condition {5 if (!condition) throw new Error(message);6}78Deno.test("empty environment selects local-only mode", () => {9 const result = validateEnvironment({});10 assert(result.ok, "empty local mode should be valid");11 assert(result.mode === "local-only", "expected local-only mode");12});1314Deno.test("cloud configuration requires the complete public pair", () => {15 const result = validateEnvironment({ JAZZ_APP_ID: "app" });16 assert(!result.ok, "incomplete cloud configuration should be invalid");17 assert(result.mode === "invalid", "invalid config must not be cloud-configured");18 assert(result.errors.length === 1, "incomplete cloud configuration should fail");19 assert(result.errors[0].includes("JAZZ_SERVER_URL"), "error should name the missing variable");20});2122Deno.test("cloud configuration validates its URL without printing values", () => {23 const result = validateEnvironment({24 JAZZ_APP_ID: "app",25 JAZZ_SERVER_URL: "not-a-url",26 });27 assert(!result.ok, "invalid URL should produce an invalid result");28 assert(result.errors.length === 1, "invalid URL should fail");29 assert(!result.errors[0].includes("not-a-url"), "error must not echo configuration values");30});3132Deno.test("validated cloud result projects only the complete public pair", () => {33 const result = validateEnvironment({34 JAZZ_APP_ID: "app",35 JAZZ_SERVER_URL: "https://example.test",36 JAZZ_ADMIN_SECRET: "admin-secret",37 BACKEND_SECRET: "backend-secret",38 });39 assert(result.ok && result.mode === "cloud-configured", "cloud pair should validate");40 assert(result.client.JAZZ_APP_ID === "app", "public app ID should be projected");41 assert(!("JAZZ_ADMIN_SECRET" in result.client), "admin secret must not be projected");42 assert(!("BACKEND_SECRET" in result.client), "backend secret must not be projected");43});4445Deno.test("dotenv parsing is allowlisted and process values win", () => {46 const parsed = parseDotenv(`47 JAZZ_APP_ID=file-app48 JAZZ_SERVER_URL="https://file.example"49 UNRELATED_SECRET=ignored50 `);51 const merged = mergeEnvironment(52 parsed,53 (name) => name === "JAZZ_APP_ID" ? "process-app" : undefined,54 );55 assert(merged.JAZZ_APP_ID === "process-app", "process environment must take precedence");56 assert(57 merged.JAZZ_SERVER_URL === "https://file.example",58 "file value should fill missing process value",59 );60 assert(!("UNRELATED_SECRET" in merged), "unrelated variables must never enter the projection");61});6263Deno.test("optional env file changes mode without exposing values", async () => {64 const path = await Deno.makeTempFile({ dir: ".", prefix: ".env.contract-test-" });65 try {66 await Deno.writeTextFile(67 path,68 "JAZZ_APP_ID=file-app\nJAZZ_SERVER_URL=https://file.example\n",69 );70 const loaded = await loadEnvironment(path, () => undefined);71 const result = validateEnvironment(loaded);72 assert(result.ok && result.mode === "cloud-configured", "env file should configure cloud mode");73 } finally {74 await Deno.remove(path);75 }76});