/** * Unit tests for CDP target resolution — no browser and no network required. * * This is fork-specific logic (see FORK.md): upstream only ever drives a * browser on localhost. The precedence here is deliberately asymmetric — an * explicitly configured endpoint must never silently fall back to a different * browser, while the built-in default must, so a machine off the wire network * still gets the local browser the extension was originally written for. * * Run: npx tsx test/cdp/endpoint-test.ts */ import { DEFAULT_REMOTE_CDP, describeCdpTarget, resolveCdpTarget } from "../../src/cdp/endpoint"; let passed = 0; let failed = 0; const check = (cond: boolean, label: string): void => { if (cond) { passed++; console.log(` ✓ ${label}`); } else { failed++; console.error(` ✗ ${label}`); } }; const env = (value?: string): NodeJS.ProcessEnv => (value === undefined ? {} : { BU_CDP_HTTP: value }); function main(): void { // E1: the built-in default, used when nothing is configured. { const t = resolveCdpTarget(env()); check(t.kind === "remote", "E1: unset BU_CDP_HTTP resolves to a remote target"); if (t.kind === "remote") { check(t.source === "default", "E1: it is marked as the built-in default, not an instruction"); check(`${t.host}:${t.port}` === DEFAULT_REMOTE_CDP, "E1: it matches DEFAULT_REMOTE_CDP"); } } // E2: DEFAULT_REMOTE_CDP must itself be parseable, or the default silently // degrades to local for everyone. { const t = resolveCdpTarget(env()); check(t.kind !== "local", "E2: the built-in default constant is well-formed"); } // E3: an explicit endpoint is an instruction — source must reflect that, so // discovery fails loudly rather than falling back. { const t = resolveCdpTarget(env("10.0.0.9:9333")); check(t.kind === "remote", "E3: an explicit host:port resolves to a remote target"); if (t.kind === "remote") { check(t.host === "10.0.0.9" && t.port === 9333, "E3: host and port are parsed"); check(t.source === "env", "E3: it is marked as explicitly configured"); } } // E4: opting back into upstream behaviour. for (const alias of ["local", "LOCAL", "off", "none", "0", "no"]) { const t = resolveCdpTarget(env(alias)); check(t.kind === "local", `E4: "${alias}" selects this machine's own browser`); } // E5: empty or whitespace is treated as unset rather than as an error — a // shell exporting an empty var should not break the browser. for (const blank of ["", " "]) { const t = resolveCdpTarget(env(blank)); check(t.kind === "remote" && t.source === "default", `E5: ${JSON.stringify(blank)} falls back to the default`); } // E6: malformed values are rejected, not coerced. Silently reinterpreting // these would drive an unexpected browser. for (const bad of ["nonsense", "host:", ":9222", "host:0", "host:70000", "host:abc"]) { const t = resolveCdpTarget(env(bad)); check(t.kind === "invalid", `E6: "${bad}" is rejected as malformed`); } // E7: IPv6 and hostnames still work — the port is taken from the LAST colon. { const t = resolveCdpTarget(env("castle.local:9223")); check(t.kind === "remote" && t.host === "castle.local", "E7: hostnames are accepted"); const v6 = resolveCdpTarget(env("[::1]:9223")); check(v6.kind === "remote" && v6.host === "[::1]" && v6.port === 9223, "E7: bracketed IPv6 keeps its host"); } // E8: descriptions are for humans in /browser-status; they must distinguish // a default from an instruction, since only one of them falls back. { const d = describeCdpTarget(resolveCdpTarget(env())); const e = describeCdpTarget(resolveCdpTarget(env("10.0.0.9:9333"))); check(d.includes("default"), "E8: the default is labelled as such"); check(e.includes("BU_CDP_HTTP"), "E8: an explicit endpoint names the variable that set it"); check(describeCdpTarget(resolveCdpTarget(env("local"))).includes("own browser"), "E8: local is described plainly"); } console.log(`\n${passed} passed, ${failed} failed`); if (failed > 0) process.exit(1); } main();