/** * 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, isValidTargetValue, 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"); } // E9: the stored choice (/browser-target) sits between env and the default. { const t = resolveCdpTarget({}, "local"); check(t.kind === "local" && t.source === "stored", "E9: a stored value is used when the env is unset"); const r = resolveCdpTarget({}, "10.2.3.4:9224"); check(r.kind === "remote" && r.source === "stored", "E9: a stored host:port resolves as a remote target"); } // E10: the environment wins, so a one-off `BU_CDP_HTTP=… pi` is never // silently overridden by a setting made days ago. { const t = resolveCdpTarget(env("local"), "10.2.3.4:9224"); check(t.kind === "local" && t.source === "env", "E10: env beats the stored value"); const r = resolveCdpTarget(env("10.9.9.9:1234"), "local"); check(r.kind === "remote" && r.host === "10.9.9.9" && r.source === "env", "E10: an env endpoint beats stored local"); } // E11: clearing the stored value falls back to the built-in default rather // than to local — clearing is "use the usual browser", not "go local". { const t = resolveCdpTarget({}, null); check(t.kind === "remote" && t.source === "default", "E11: no stored value falls back to the default"); const blank = resolveCdpTarget({}, " "); check(blank.kind === "remote" && blank.source === "default", "E11: a blank stored value is treated as cleared"); } // E12: a corrupt stored value must be reported against the right source, or // the operator goes hunting through their shell config for an env var that // was never set. { const t = resolveCdpTarget({}, "garbage"); check(t.kind === "invalid" && t.source === "stored", "E12: a bad stored value is blamed on /browser-target"); if (t.kind === "invalid") { check(describeCdpTarget(t).includes("/browser-target"), "E12: the description names the stored source"); } const e = resolveCdpTarget(env("garbage"), null); check(e.kind === "invalid" && e.source === "env", "E12: a bad env value is blamed on BU_CDP_HTTP"); } // E13: the values /browser-target accepts must match what resolution can // actually use, or the command would happily store something unusable. { for (const good of ["local", "off", "1.2.3.4:9222", "castle.local:9223"]) { check(isValidTargetValue(good), `E13: "${good}" is accepted by the command`); } for (const bad of ["", " ", "garbage", "host:", "host:99999"]) { check(!isValidTargetValue(bad), `E13: "${bad}" is rejected by the command`); } check(!isValidTargetValue("default"), "E13: \"default\" is handled as clear, not stored verbatim"); } console.log(`\n${passed} passed, ${failed} failed`); if (failed > 0) process.exit(1); } main();