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
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:
+53
-14
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user