aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorIgor Soarez <igor@soarez.org>2026-07-28 22:08:44 +0100
committerIgor Soarez <igor@soarez.org>2026-10-14 12:17:59 +0100
commit95ff5e8eaefb35d79109f7ddfa1de5bf45a8daff (patch)
tree397c6cde867cb5028fcc5d00c1eb84d8a949da50 /test
parentbfb533fc9a829819b9dd425607101d40c676d45c (diff)
feat(cdp): default to the shared browser, and say so in /browser-status
Two follow-ups to BU_CDP_HTTP. Default the endpoint. The remote host is now a built-in default (DEFAULT_REMOTE_CDP), so nothing has to export BU_CDP_HTTP just to get the usual setup. The variable becomes an override rather than a requirement. Precedence is deliberately asymmetric, in src/cdp/endpoint.ts: BU_CDP_HTTP=host:port that endpoint; unreachable is a hard error BU_CDP_HTTP=local|off upstream behaviour, this machine's own browser unset the default, falling back to local when unreachable An explicitly configured endpoint is an instruction, so failing to reach it must not quietly automate a different browser - that would drive the operator's own signed-in Chrome instead of the shared one. The built-in default is only a preference, so a laptop away from the network still gets the local browser the extension was originally written for. Resolution is pure, with no I/O, so the status command can report the configured intent without touching the network and the daemon can resolve the same way in its own process. /browser-status no longer reports a bare "Disconnected". The connection is lazy - nothing dials the browser until the first browser_* tool runs - so a fresh session always showed a red light that reads as a fault. It now distinguishes "not connected yet, connects on first use" from "target unreachable", and names the target with a reachability check, so the common confusion answers itself. 26 tests for the resolution table, covering the aliases, malformed values that must be rejected rather than coerced, and that the default constant is itself well-formed - if it ever stopped parsing, every machine would silently fall back to local.
Diffstat (limited to 'test')
-rw-r--r--test/cdp/endpoint-test.ts99
1 files changed, 99 insertions, 0 deletions
diff --git a/test/cdp/endpoint-test.ts b/test/cdp/endpoint-test.ts
new file mode 100644
index 0000000..d4b696f
--- /dev/null
+++ b/test/cdp/endpoint-test.ts
@@ -0,0 +1,99 @@
+/**
+ * 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();