264 lines
9.8 KiB
Python
264 lines
9.8 KiB
Python
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from browser_cli.client import (
|
|
BrowserNotConnected,
|
|
BrowserTarget,
|
|
_resolve_socket,
|
|
active_browser_targets,
|
|
display_browser_name,
|
|
save_remote_token,
|
|
send_command,
|
|
remote_target_for_alias,
|
|
token_for_remote,
|
|
)
|
|
from browser_cli.platform import endpoint_for_alias
|
|
|
|
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"))
|
|
|
|
with pytest.raises(BrowserNotConnected, match="Cannot resolve a browser socket automatically"):
|
|
_resolve_socket()
|
|
|
|
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("")
|
|
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)
|
|
|
|
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("")
|
|
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()
|
|
|
|
|
|
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("")
|
|
stale_socket = tmp_path / "stale.sock"
|
|
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)
|
|
|
|
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"}))
|
|
|
|
monkeypatch.setattr("browser_cli.client.REGISTRY_PATH", registry_path)
|
|
monkeypatch.setattr("browser_cli.client.is_windows", lambda: True)
|
|
|
|
targets = active_browser_targets(include_remotes=False)
|
|
|
|
assert len(targets) == 1
|
|
assert targets[0].socket_path == r"\\.\pipe\browser-cli-work"
|
|
|
|
|
|
def test_save_remote_token_persists_per_endpoint(monkeypatch, tmp_path):
|
|
remotes_path = tmp_path / "remotes.json"
|
|
monkeypatch.setattr("browser_cli.client.REMOTE_REGISTRY_PATH", remotes_path)
|
|
|
|
endpoint = "browser-host.example:8765"
|
|
|
|
save_remote_token(endpoint, "secret-token")
|
|
|
|
assert token_for_remote(endpoint) == "secret-token"
|
|
assert json.loads(remotes_path.read_text(encoding="utf-8")) == {
|
|
endpoint: {"token": "secret-token"}
|
|
}
|
|
|
|
|
|
def test_send_command_auto_routes_single_remote_target(monkeypatch):
|
|
monkeypatch.delenv("BROWSER_CLI_PROFILE", raising=False)
|
|
monkeypatch.delenv("BROWSER_CLI_REMOTE", raising=False)
|
|
monkeypatch.delenv("BROWSER_CLI_TOKEN", raising=False)
|
|
sent = {}
|
|
|
|
monkeypatch.setattr(
|
|
"browser_cli.client.remote_browser_targets",
|
|
lambda endpoint, token=None: [BrowserTarget("work", "host:work", "", remote=endpoint, token=token)],
|
|
)
|
|
|
|
def fake_send_remote(endpoint, framed):
|
|
payload_len = int.from_bytes(framed[:4], "little")
|
|
msg = json.loads(framed[4:4 + payload_len])
|
|
sent.update(msg)
|
|
return json.dumps({"success": True, "data": "ok"}).encode("utf-8")
|
|
|
|
monkeypatch.setattr("browser_cli.client._send_remote", fake_send_remote)
|
|
|
|
assert send_command("tabs.list", remote="host:8765", token="secret") == "ok"
|
|
assert sent["_route"] == "work"
|
|
assert sent["token"] == "secret"
|
|
|
|
|
|
def test_send_command_resolves_browser_alias_to_remote_target(monkeypatch):
|
|
monkeypatch.delenv("BROWSER_CLI_REMOTE", raising=False)
|
|
monkeypatch.delenv("BROWSER_CLI_TOKEN", raising=False)
|
|
monkeypatch.setenv("BROWSER_CLI_PROFILE", "host:work")
|
|
sent = {}
|
|
|
|
monkeypatch.setattr(
|
|
"browser_cli.client._remote_browser_targets",
|
|
lambda: [BrowserTarget("work", "host:work", "", remote="host:8765", token="secret")],
|
|
)
|
|
|
|
def fake_send_remote(endpoint, framed):
|
|
payload_len = int.from_bytes(framed[:4], "little")
|
|
msg = json.loads(framed[4:4 + payload_len])
|
|
sent["endpoint"] = endpoint
|
|
sent.update(msg)
|
|
return json.dumps({"success": True, "data": []}).encode("utf-8")
|
|
|
|
monkeypatch.setattr("browser_cli.client._send_remote", fake_send_remote)
|
|
|
|
assert send_command("tabs.list") == []
|
|
assert sent["endpoint"] == "host:8765"
|
|
assert sent["_route"] == "work"
|
|
assert sent["token"] == "secret"
|
|
|
|
|
|
def test_remote_target_for_alias_accepts_full_endpoint_profile(monkeypatch):
|
|
monkeypatch.setattr(
|
|
"browser_cli.client._remote_browser_targets",
|
|
lambda: [BrowserTarget("work", "host:work", "", remote="host:8765", token="secret")],
|
|
)
|
|
|
|
target = remote_target_for_alias("host:8765:work")
|
|
|
|
assert target is not None
|
|
assert target.profile == "work"
|
|
assert target.remote == "host:8765"
|
|
|
|
|
|
def test_remote_target_for_alias_accepts_host_when_only_one_remote_target(monkeypatch):
|
|
remote_host = "browser-host.example"
|
|
remote_endpoint = f"{remote_host}:8765"
|
|
monkeypatch.setattr(
|
|
"browser_cli.client._remote_browser_targets",
|
|
lambda: [BrowserTarget("work", f"{remote_host}:work", "", remote=remote_endpoint, token="secret")],
|
|
)
|
|
|
|
target = remote_target_for_alias(remote_host)
|
|
|
|
assert target is not None
|
|
assert target.profile == "work"
|
|
assert target.remote == remote_endpoint
|
|
|
|
|
|
def test_send_command_resolves_host_alias_to_single_remote_target(monkeypatch):
|
|
remote_host = "browser-host.example"
|
|
remote_endpoint = f"{remote_host}:8765"
|
|
monkeypatch.delenv("BROWSER_CLI_REMOTE", raising=False)
|
|
monkeypatch.delenv("BROWSER_CLI_TOKEN", raising=False)
|
|
monkeypatch.setenv("BROWSER_CLI_PROFILE", remote_host)
|
|
sent = {}
|
|
|
|
monkeypatch.setattr(
|
|
"browser_cli.client._remote_browser_targets",
|
|
lambda: [BrowserTarget("work", f"{remote_host}:work", "", remote=remote_endpoint, token="secret")],
|
|
)
|
|
|
|
def fake_send_remote(endpoint, framed):
|
|
payload_len = int.from_bytes(framed[:4], "little")
|
|
msg = json.loads(framed[4:4 + payload_len])
|
|
sent["endpoint"] = endpoint
|
|
sent.update(msg)
|
|
return json.dumps({"success": True, "data": []}).encode("utf-8")
|
|
|
|
monkeypatch.setattr("browser_cli.client._send_remote", fake_send_remote)
|
|
|
|
assert send_command("tabs.list") == []
|
|
assert sent["endpoint"] == remote_endpoint
|
|
assert sent["_route"] == "work"
|
|
assert sent["token"] == "secret"
|
|
|
|
|
|
def test_remote_target_for_alias_keeps_host_alias_ambiguous_for_multiple_targets(monkeypatch):
|
|
monkeypatch.setattr(
|
|
"browser_cli.client._remote_browser_targets",
|
|
lambda: [
|
|
BrowserTarget("main", "host:main", "", remote="host:8765", token="secret"),
|
|
BrowserTarget("work", "host:work", "", remote="host:8765", token="secret"),
|
|
],
|
|
)
|
|
|
|
assert remote_target_for_alias("host") is None
|
|
|
|
|
|
def test_send_command_requires_browser_for_multiple_remote_targets(monkeypatch):
|
|
monkeypatch.delenv("BROWSER_CLI_PROFILE", raising=False)
|
|
monkeypatch.setattr(
|
|
"browser_cli.client.remote_browser_targets",
|
|
lambda endpoint, token=None: [
|
|
BrowserTarget("main", "host:main", "", remote=endpoint, token=token),
|
|
BrowserTarget("furry", "host:furry", "", remote=endpoint, token=token),
|
|
],
|
|
)
|
|
|
|
with pytest.raises(BrowserNotConnected, match="Multiple remote browser instances are active: main, furry"):
|
|
send_command("tabs.list", remote="host:8765", token="secret")
|
|
|
|
|
|
def test_active_browser_targets_includes_remote_targets(monkeypatch, tmp_path):
|
|
remotes_path = tmp_path / "remotes.json"
|
|
endpoint = "browser-host.example:8765"
|
|
remotes_path.write_text(json.dumps({endpoint: {"token": "secret-token"}}), encoding="utf-8")
|
|
monkeypatch.setattr("browser_cli.client.REGISTRY_PATH", tmp_path / "missing-registry.json")
|
|
monkeypatch.setattr("browser_cli.client.REMOTE_REGISTRY_PATH", remotes_path)
|
|
|
|
def fake_send_command(command, args=None, profile=None, remote=None, token=None):
|
|
assert command == "browser-cli.targets"
|
|
assert remote == endpoint
|
|
assert token == "secret-token"
|
|
return [{"profile": "work", "displayName": "work"}]
|
|
|
|
monkeypatch.setattr("browser_cli.client.send_command", fake_send_command)
|
|
|
|
targets = active_browser_targets()
|
|
|
|
assert len(targets) == 1
|
|
assert targets[0].profile == "work"
|
|
assert targets[0].display_name == "browser-host.example:work"
|
|
assert targets[0].remote == endpoint
|
|
assert targets[0].token == "secret-token"
|