fix(client): validate active browser endpoints
Testing / remote-protocol-compat (0.9.3) (push) Successful in 42s
Testing / remote-protocol-compat (0.9.5) (push) Successful in 41s
Testing / test (push) Successful in 50s
Package Extension / package-extension (push) Successful in 29s
Build & Publish Package / publish (push) Successful in 33s

- Check Unix socket reachability with a real connection attempt instead of treating any existing path as active.
- Report ambiguous host-only remote aliases with actionable --remote/--browser examples.
- Update client tests to use listening Unix sockets and cover ambiguous remote alias errors.
- Bump package and extension versions to 0.10.2.
This commit is contained in:
2026-05-25 17:19:17 +02:00
parent 93f8994f6a
commit d2f2a99f3d
5 changed files with 86 additions and 19 deletions
+53 -14
View File
@@ -1,4 +1,5 @@
import json
import socket
from pathlib import Path
import pytest
@@ -18,6 +19,12 @@ from browser_cli.client import (
)
from browser_cli.platform import endpoint_for_alias
def _listening_unix_socket(path: Path) -> socket.socket:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.bind(str(path))
sock.listen(1)
return sock
def test_resolve_socket_raises_when_registry_missing(monkeypatch):
monkeypatch.delenv("BROWSER_CLI_PROFILE", raising=False)
monkeypatch.setattr("browser_cli.client.REGISTRY_PATH", Path("/nonexistent/browser-cli-registry.json"))
@@ -29,59 +36,67 @@ def test_resolve_socket_uses_only_active_registry_entry(monkeypatch, tmp_path):
monkeypatch.delenv("BROWSER_CLI_PROFILE", raising=False)
socket_path = tmp_path / "browser.sock"
socket_path.write_text("")
listener = _listening_unix_socket(socket_path)
registry_path = tmp_path / "registry.json"
registry_path.write_text(json.dumps({"abc-uuid": str(socket_path)}))
monkeypatch.setattr("browser_cli.client.REGISTRY_PATH", registry_path)
assert _resolve_socket() == str(socket_path)
try:
assert _resolve_socket() == str(socket_path)
finally:
listener.close()
def test_resolve_socket_raises_when_multiple_active_entries(monkeypatch, tmp_path):
monkeypatch.delenv("BROWSER_CLI_PROFILE", raising=False)
first_socket = tmp_path / "one.sock"
second_socket = tmp_path / "two.sock"
first_socket.write_text("")
second_socket.write_text("")
first_listener = _listening_unix_socket(first_socket)
second_listener = _listening_unix_socket(second_socket)
registry_path = tmp_path / "registry.json"
registry_path.write_text(json.dumps({"uuid-1": str(first_socket), "uuid-2": str(second_socket)}))
monkeypatch.setattr("browser_cli.client.REGISTRY_PATH", registry_path)
with pytest.raises(BrowserNotConnected, match="Multiple browser instances are active: uuid-1, uuid-2"):
_resolve_socket()
try:
with pytest.raises(BrowserNotConnected, match="Multiple browser instances are active: uuid-1, uuid-2"):
_resolve_socket()
finally:
first_listener.close()
second_listener.close()
def test_display_browser_name_uses_uuid_stem_for_default():
assert display_browser_name("default", "/tmp/.browser_cli/550e8400-e29b-41d4-a716-446655440000.sock") == (
"550e8400-e29b-41d4-a716-446655440000"
)
def test_resolve_socket_uses_platform_endpoint_for_explicit_alias(monkeypatch):
monkeypatch.delenv("BROWSER_CLI_PROFILE", raising=False)
monkeypatch.setattr("browser_cli.client.REGISTRY_PATH", Path("/nonexistent/browser-cli-registry.json"))
assert _resolve_socket("work") == endpoint_for_alias("work")
def test_active_browser_targets_filters_stale_entries(monkeypatch, tmp_path):
active_socket = tmp_path / "work.sock"
active_socket.write_text("")
listener = _listening_unix_socket(active_socket)
stale_socket = tmp_path / "stale.sock"
stale_listener = _listening_unix_socket(stale_socket)
stale_listener.close()
registry_path = tmp_path / "registry.json"
registry_path.write_text(json.dumps({"work": str(active_socket), "default": str(stale_socket)}))
monkeypatch.setattr("browser_cli.client.REGISTRY_PATH", registry_path)
targets = active_browser_targets(include_remotes=False)
try:
targets = active_browser_targets(include_remotes=False)
finally:
listener.close()
assert len(targets) == 1
assert targets[0].profile == "work"
assert targets[0].display_name == "work"
def test_active_browser_targets_keeps_windows_registry_entries(monkeypatch, tmp_path):
registry_path = tmp_path / "registry.json"
registry_path.write_text(json.dumps({"work": r"\\.\pipe\browser-cli-work"}))
@@ -129,6 +144,7 @@ def test_send_command_prefers_active_local_profile_over_saved_remote_alias(monke
"browser_cli.client.remote_target_for_alias",
lambda alias: pytest.fail("active local profile must not trigger remote alias discovery"),
)
monkeypatch.setattr("browser_cli.client._is_active_local_profile", lambda profile: True)
payload = json.dumps({"success": True, "data": "local-ok"}).encode("utf-8")
framed = len(payload).to_bytes(4, "little") + payload
@@ -237,7 +253,7 @@ def test_send_command_resolves_host_alias_to_single_remote_target(monkeypatch):
assert "token" not in sent
def test_remote_target_for_alias_keeps_host_alias_ambiguous_for_multiple_targets(monkeypatch):
def test_remote_target_for_alias_reports_ambiguous_host_for_multiple_targets(monkeypatch):
monkeypatch.setattr(
"browser_cli.client._remote_browser_targets",
lambda: [
@@ -246,7 +262,30 @@ def test_remote_target_for_alias_keeps_host_alias_ambiguous_for_multiple_targets
],
)
assert remote_target_for_alias("host") is None
with pytest.raises(BrowserNotConnected) as exc:
remote_target_for_alias("host")
msg = str(exc.value)
assert "Multiple remote browser instances are active on host: main, work" in msg
assert "browser-cli --remote host:8765 --browser main" in msg
assert "browser-cli --browser host:work" in msg
def test_send_command_reports_ambiguous_remote_browser_alias(monkeypatch):
monkeypatch.delenv("BROWSER_CLI_REMOTE", raising=False)
monkeypatch.setenv("BROWSER_CLI_PROFILE", "host")
monkeypatch.setattr("browser_cli.client.REGISTRY_PATH", Path("/nonexistent/browser-cli-registry.json"))
monkeypatch.setattr("browser_cli.client.is_windows", lambda: False)
monkeypatch.setattr(
"browser_cli.client._remote_browser_targets",
lambda: [
BrowserTarget("main", "host:main", "", remote="host:8765"),
BrowserTarget("work", "host:work", "", remote="host:8765"),
],
)
with pytest.raises(BrowserNotConnected, match="Multiple remote browser instances are active on host: main, work"):
send_command("tabs.list")
def test_send_command_requires_browser_for_multiple_remote_targets(monkeypatch):