allow to ask for remote host profiles and save token on first connection for later use
This commit is contained in:
@@ -54,7 +54,7 @@ class BrowserCLI:
|
|||||||
if self._browser is not None:
|
if self._browser is not None:
|
||||||
return []
|
return []
|
||||||
targets = active_browser_targets()
|
targets = active_browser_targets()
|
||||||
if len(targets) <= 1:
|
if len(targets) <= 1 and not any(target.remote for target in targets):
|
||||||
return []
|
return []
|
||||||
return targets
|
return targets
|
||||||
|
|
||||||
@@ -62,6 +62,9 @@ class BrowserCLI:
|
|||||||
results = []
|
results = []
|
||||||
for target in self._multi_browser_targets():
|
for target in self._multi_browser_targets():
|
||||||
try:
|
try:
|
||||||
|
if target.remote:
|
||||||
|
data = send_command(command, args, profile=target.profile, remote=target.remote, token=target.token)
|
||||||
|
else:
|
||||||
data = send_command(command, args, profile=target.profile)
|
data = send_command(command, args, profile=target.profile)
|
||||||
except (BrowserNotConnected, RuntimeError):
|
except (BrowserNotConnected, RuntimeError):
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ from browser_cli.client import (
|
|||||||
REGISTRY_PATH,
|
REGISTRY_PATH,
|
||||||
active_browser_targets,
|
active_browser_targets,
|
||||||
display_browser_name,
|
display_browser_name,
|
||||||
|
save_remote_token,
|
||||||
)
|
)
|
||||||
from browser_cli.platform import install_base_dir, is_windows
|
from browser_cli.platform import install_base_dir, is_windows
|
||||||
|
|
||||||
@@ -185,6 +186,8 @@ def main(ctx, browser, remote, token):
|
|||||||
ctx.obj["token"] = token
|
ctx.obj["token"] = token
|
||||||
if remote:
|
if remote:
|
||||||
os.environ["BROWSER_CLI_REMOTE"] = remote
|
os.environ["BROWSER_CLI_REMOTE"] = remote
|
||||||
|
if token:
|
||||||
|
save_remote_token(remote, token)
|
||||||
if token:
|
if token:
|
||||||
os.environ["BROWSER_CLI_TOKEN"] = token
|
os.environ["BROWSER_CLI_TOKEN"] = token
|
||||||
|
|
||||||
@@ -249,6 +252,17 @@ def clients_group(ctx):
|
|||||||
"extensionVersion": "disconnected",
|
"extensionVersion": "disconnected",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
for target in active_browser_targets():
|
||||||
|
if target.remote is None:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
result = send_command("clients.list", profile=target.profile, remote=target.remote, token=target.token)
|
||||||
|
for c in (result or []):
|
||||||
|
c["profile"] = target.display_name
|
||||||
|
all_clients.append(c)
|
||||||
|
except (BrowserNotConnected, RuntimeError):
|
||||||
|
continue
|
||||||
|
|
||||||
if not all_clients:
|
if not all_clients:
|
||||||
console.print("[yellow]No browser clients found. Start a browser with the extension enabled first.[/yellow]")
|
console.print("[yellow]No browser clients found. Start a browser with the extension enabled first.[/yellow]")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|||||||
+79
-8
@@ -21,6 +21,7 @@ from typing import Any
|
|||||||
from browser_cli.platform import endpoint_for_alias, is_windows, registry_path
|
from browser_cli.platform import endpoint_for_alias, is_windows, registry_path
|
||||||
|
|
||||||
REGISTRY_PATH = registry_path()
|
REGISTRY_PATH = registry_path()
|
||||||
|
REMOTE_REGISTRY_PATH = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) / "browser-cli" / "remotes.json"
|
||||||
|
|
||||||
|
|
||||||
class BrowserNotConnected(Exception):
|
class BrowserNotConnected(Exception):
|
||||||
@@ -32,6 +33,8 @@ class BrowserTarget:
|
|||||||
profile: str
|
profile: str
|
||||||
display_name: str
|
display_name: str
|
||||||
socket_path: str
|
socket_path: str
|
||||||
|
remote: str | None = None
|
||||||
|
token: str | None = None
|
||||||
|
|
||||||
|
|
||||||
def _active_endpoints(reg: dict) -> dict:
|
def _active_endpoints(reg: dict) -> dict:
|
||||||
@@ -47,17 +50,85 @@ def display_browser_name(profile_name: str, sock_path: str) -> str:
|
|||||||
return Path(sock_path).stem or profile_name
|
return Path(sock_path).stem or profile_name
|
||||||
|
|
||||||
|
|
||||||
def active_browser_targets() -> list[BrowserTarget]:
|
def _load_remotes() -> dict[str, dict[str, str]]:
|
||||||
if not REGISTRY_PATH.exists():
|
if not REMOTE_REGISTRY_PATH.exists():
|
||||||
return []
|
return {}
|
||||||
|
try:
|
||||||
|
data = json.loads(REMOTE_REGISTRY_PATH.read_text(encoding="utf-8"))
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
return {}
|
||||||
|
return {str(endpoint): cfg for endpoint, cfg in data.items() if isinstance(cfg, dict)}
|
||||||
|
|
||||||
|
|
||||||
|
def save_remote_token(endpoint: str, token: str | None) -> None:
|
||||||
|
"""Persist the auth token for a remote endpoint used by this client."""
|
||||||
|
if not endpoint or not token:
|
||||||
|
return
|
||||||
|
remotes = _load_remotes()
|
||||||
|
current = remotes.get(endpoint, {})
|
||||||
|
current["token"] = token
|
||||||
|
remotes[endpoint] = current
|
||||||
|
REMOTE_REGISTRY_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
REMOTE_REGISTRY_PATH.write_text(json.dumps(remotes, indent=2, sort_keys=True), encoding="utf-8")
|
||||||
|
try:
|
||||||
|
REMOTE_REGISTRY_PATH.chmod(0o600)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def token_for_remote(endpoint: str | None) -> str | None:
|
||||||
|
if not endpoint:
|
||||||
|
return None
|
||||||
|
cfg = _load_remotes().get(endpoint) or {}
|
||||||
|
token = cfg.get("token")
|
||||||
|
return str(token) if token else None
|
||||||
|
|
||||||
|
|
||||||
|
def _remote_display_name(endpoint: str, profile_name: str, display_name: str) -> str:
|
||||||
|
host, sep, port = endpoint.rpartition(":")
|
||||||
|
remote_name = host if sep and port == "8765" else endpoint
|
||||||
|
return f"{remote_name}:{display_name or profile_name}"
|
||||||
|
|
||||||
|
|
||||||
|
def _remote_browser_targets() -> list[BrowserTarget]:
|
||||||
|
targets: list[BrowserTarget] = []
|
||||||
|
for endpoint, cfg in _load_remotes().items():
|
||||||
|
token = str(cfg.get("token") or "") or None
|
||||||
|
try:
|
||||||
|
remote_targets = send_command("browser-cli.targets", remote=endpoint, token=token)
|
||||||
|
except (BrowserNotConnected, RuntimeError):
|
||||||
|
continue
|
||||||
|
for item in remote_targets or []:
|
||||||
|
profile = str(item.get("profile") or "default")
|
||||||
|
display = str(item.get("displayName") or profile)
|
||||||
|
targets.append(
|
||||||
|
BrowserTarget(
|
||||||
|
profile=profile,
|
||||||
|
display_name=_remote_display_name(endpoint, profile, display),
|
||||||
|
socket_path="",
|
||||||
|
remote=endpoint,
|
||||||
|
token=token,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return targets
|
||||||
|
|
||||||
|
|
||||||
|
def active_browser_targets(*, include_remotes: bool = True) -> list[BrowserTarget]:
|
||||||
|
targets: list[BrowserTarget] = []
|
||||||
|
if REGISTRY_PATH.exists():
|
||||||
try:
|
try:
|
||||||
reg = json.loads(REGISTRY_PATH.read_text())
|
reg = json.loads(REGISTRY_PATH.read_text())
|
||||||
except Exception:
|
except Exception:
|
||||||
return []
|
reg = {}
|
||||||
return [
|
targets.extend(
|
||||||
BrowserTarget(profile=profile, display_name=display_browser_name(profile, sock_path), socket_path=sock_path)
|
BrowserTarget(profile=profile, display_name=display_browser_name(profile, sock_path), socket_path=sock_path)
|
||||||
for profile, sock_path in _active_endpoints(reg).items()
|
for profile, sock_path in _active_endpoints(reg).items()
|
||||||
]
|
)
|
||||||
|
if include_remotes:
|
||||||
|
targets.extend(_remote_browser_targets())
|
||||||
|
return targets
|
||||||
|
|
||||||
|
|
||||||
def _resolve_socket(profile: str | None = None) -> str:
|
def _resolve_socket(profile: str | None = None) -> str:
|
||||||
@@ -76,7 +147,7 @@ def _resolve_socket(profile: str | None = None) -> str:
|
|||||||
|
|
||||||
# Auto-detect: error when multiple browser instances are active
|
# Auto-detect: error when multiple browser instances are active
|
||||||
try:
|
try:
|
||||||
active = active_browser_targets()
|
active = active_browser_targets(include_remotes=False)
|
||||||
if len(active) > 1:
|
if len(active) > 1:
|
||||||
aliases = [target.profile for target in active]
|
aliases = [target.profile for target in active]
|
||||||
examples = "\n".join(f" browser-cli --browser {a} ..." for a in aliases)
|
examples = "\n".join(f" browser-cli --browser {a} ..." for a in aliases)
|
||||||
@@ -101,7 +172,7 @@ def _resolve_socket(profile: str | None = None) -> str:
|
|||||||
def send_command(command: str, args: dict | None = None, profile: str | None = None, remote: str | None = None, token: str | None = None) -> Any:
|
def send_command(command: str, args: dict | None = None, profile: str | None = None, remote: str | None = None, token: str | None = None) -> Any:
|
||||||
"""Send a command to the browser and return the response data."""
|
"""Send a command to the browser and return the response data."""
|
||||||
remote_endpoint = remote or os.environ.get("BROWSER_CLI_REMOTE")
|
remote_endpoint = remote or os.environ.get("BROWSER_CLI_REMOTE")
|
||||||
resolved_token = token or os.environ.get("BROWSER_CLI_TOKEN")
|
resolved_token = token or os.environ.get("BROWSER_CLI_TOKEN") or token_for_remote(remote_endpoint)
|
||||||
msg = {
|
msg = {
|
||||||
"id": str(uuid.uuid4()),
|
"id": str(uuid.uuid4()),
|
||||||
"command": command,
|
"command": command,
|
||||||
|
|||||||
@@ -17,8 +17,10 @@ def _handle(command, args=None, profile=None):
|
|||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
|
||||||
def _handle_multi(command, args=None, profile=None):
|
def _handle_multi(command, args=None, profile=None, remote=None, token=None):
|
||||||
try:
|
try:
|
||||||
|
if remote:
|
||||||
|
return send_command(command, args or {}, profile=profile, remote=remote, token=token)
|
||||||
return send_command(command, args or {}, profile=profile)
|
return send_command(command, args or {}, profile=profile)
|
||||||
except (BrowserNotConnected, RuntimeError):
|
except (BrowserNotConnected, RuntimeError):
|
||||||
return None
|
return None
|
||||||
@@ -29,7 +31,7 @@ def _multi_browser_targets():
|
|||||||
if root.obj.get("browser_explicit"):
|
if root.obj.get("browser_explicit"):
|
||||||
return []
|
return []
|
||||||
targets = active_browser_targets()
|
targets = active_browser_targets()
|
||||||
if len(targets) <= 1:
|
if len(targets) <= 1 and not any(target.remote for target in targets):
|
||||||
return []
|
return []
|
||||||
return targets
|
return targets
|
||||||
|
|
||||||
@@ -71,7 +73,7 @@ def group_list():
|
|||||||
if targets:
|
if targets:
|
||||||
groups = []
|
groups = []
|
||||||
for target in targets:
|
for target in targets:
|
||||||
result = _handle_multi("group.list", profile=target.profile)
|
result = _handle_multi("group.list", profile=target.profile, remote=target.remote, token=target.token)
|
||||||
if result is None:
|
if result is None:
|
||||||
continue
|
continue
|
||||||
groups.extend({**group, "browser": target.display_name} for group in result)
|
groups.extend({**group, "browser": target.display_name} for group in result)
|
||||||
@@ -104,7 +106,7 @@ def group_count():
|
|||||||
total = 0
|
total = 0
|
||||||
rows = 0
|
rows = 0
|
||||||
for target in targets:
|
for target in targets:
|
||||||
count = _handle_multi("group.count", profile=target.profile)
|
count = _handle_multi("group.count", profile=target.profile, remote=target.remote, token=target.token)
|
||||||
if count is None:
|
if count is None:
|
||||||
continue
|
continue
|
||||||
count = int(count or 0)
|
count = int(count or 0)
|
||||||
|
|||||||
@@ -56,6 +56,17 @@ def _proxy_request(client_sock:socket.socket, addr:tuple, profile:str|None, serv
|
|||||||
_log(addr, command, None, "DENIED", "bad token")
|
_log(addr, command, None, "DENIED", "bad token")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if command == "browser-cli.targets":
|
||||||
|
from browser_cli.client import active_browser_targets
|
||||||
|
targets = [
|
||||||
|
{"profile": target.profile, "displayName": target.display_name}
|
||||||
|
for target in active_browser_targets(include_remotes=False)
|
||||||
|
]
|
||||||
|
data = json.dumps({"id": msg_id, "success": True, "data": targets}).encode()
|
||||||
|
client_sock.sendall(struct.pack("<I", len(data)) + data)
|
||||||
|
_log(addr, command, None, "OK")
|
||||||
|
return
|
||||||
|
|
||||||
resolved_profile = msg.get("_route") or profile
|
resolved_profile = msg.get("_route") or profile
|
||||||
|
|
||||||
strip = {"token", "_route"}
|
strip = {"token", "_route"}
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ def _handle(command, args=None, profile=None):
|
|||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
|
||||||
def _handle_multi(command, args=None, profile=None):
|
def _handle_multi(command, args=None, profile=None, remote=None, token=None):
|
||||||
try:
|
try:
|
||||||
|
if remote:
|
||||||
|
return send_command(command, args or {}, profile=profile, remote=remote, token=token)
|
||||||
return send_command(command, args or {}, profile=profile)
|
return send_command(command, args or {}, profile=profile)
|
||||||
except (BrowserNotConnected, RuntimeError):
|
except (BrowserNotConnected, RuntimeError):
|
||||||
return None
|
return None
|
||||||
@@ -28,7 +30,7 @@ def _multi_browser_targets():
|
|||||||
if root.obj.get("browser_explicit"):
|
if root.obj.get("browser_explicit"):
|
||||||
return []
|
return []
|
||||||
targets = active_browser_targets()
|
targets = active_browser_targets()
|
||||||
if len(targets) <= 1:
|
if len(targets) <= 1 and not any(target.remote for target in targets):
|
||||||
return []
|
return []
|
||||||
return targets
|
return targets
|
||||||
|
|
||||||
@@ -92,7 +94,7 @@ def session_list():
|
|||||||
if targets:
|
if targets:
|
||||||
sessions = []
|
sessions = []
|
||||||
for target in targets:
|
for target in targets:
|
||||||
result = _handle_multi("session.list", profile=target.profile)
|
result = _handle_multi("session.list", profile=target.profile, remote=target.remote, token=target.token)
|
||||||
if result is None:
|
if result is None:
|
||||||
continue
|
continue
|
||||||
sessions.extend({**session, "browser": target.display_name} for session in result)
|
sessions.extend({**session, "browser": target.display_name} for session in result)
|
||||||
|
|||||||
@@ -18,8 +18,10 @@ def _handle(command, args=None, profile=None):
|
|||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
|
||||||
def _handle_multi(command, args=None, profile=None):
|
def _handle_multi(command, args=None, profile=None, remote=None, token=None):
|
||||||
try:
|
try:
|
||||||
|
if remote:
|
||||||
|
return send_command(command, args or {}, profile=profile, remote=remote, token=token)
|
||||||
return send_command(command, args or {}, profile=profile)
|
return send_command(command, args or {}, profile=profile)
|
||||||
except (BrowserNotConnected, RuntimeError):
|
except (BrowserNotConnected, RuntimeError):
|
||||||
return None
|
return None
|
||||||
@@ -30,7 +32,7 @@ def _multi_browser_targets():
|
|||||||
if root.obj.get("browser_explicit"):
|
if root.obj.get("browser_explicit"):
|
||||||
return []
|
return []
|
||||||
targets = active_browser_targets()
|
targets = active_browser_targets()
|
||||||
if len(targets) <= 1:
|
if len(targets) <= 1 and not any(target.remote for target in targets):
|
||||||
return []
|
return []
|
||||||
return targets
|
return targets
|
||||||
|
|
||||||
@@ -76,7 +78,7 @@ def tabs_list():
|
|||||||
if targets:
|
if targets:
|
||||||
tabs = []
|
tabs = []
|
||||||
for target in targets:
|
for target in targets:
|
||||||
result = _handle_multi("tabs.list", profile=target.profile)
|
result = _handle_multi("tabs.list", profile=target.profile, remote=target.remote, token=target.token)
|
||||||
if result is None:
|
if result is None:
|
||||||
continue
|
continue
|
||||||
tabs.extend({**tab, "browser": target.display_name} for tab in result)
|
tabs.extend({**tab, "browser": target.display_name} for tab in result)
|
||||||
@@ -163,7 +165,7 @@ def tabs_count(pattern):
|
|||||||
total = 0
|
total = 0
|
||||||
rows = 0
|
rows = 0
|
||||||
for target in targets:
|
for target in targets:
|
||||||
count = _handle_multi("tabs.count", {"pattern": pattern}, profile=target.profile)
|
count = _handle_multi("tabs.count", {"pattern": pattern}, profile=target.profile, remote=target.remote, token=target.token)
|
||||||
if count is None:
|
if count is None:
|
||||||
continue
|
continue
|
||||||
count = int(count or 0)
|
count = int(count or 0)
|
||||||
|
|||||||
@@ -17,8 +17,10 @@ def _handle(command, args=None, profile=None):
|
|||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
|
||||||
def _handle_multi(command, args=None, profile=None):
|
def _handle_multi(command, args=None, profile=None, remote=None, token=None):
|
||||||
try:
|
try:
|
||||||
|
if remote:
|
||||||
|
return send_command(command, args or {}, profile=profile, remote=remote, token=token)
|
||||||
return send_command(command, args or {}, profile=profile)
|
return send_command(command, args or {}, profile=profile)
|
||||||
except (BrowserNotConnected, RuntimeError):
|
except (BrowserNotConnected, RuntimeError):
|
||||||
return None
|
return None
|
||||||
@@ -29,7 +31,7 @@ def _multi_browser_targets():
|
|||||||
if root.obj.get("browser_explicit"):
|
if root.obj.get("browser_explicit"):
|
||||||
return []
|
return []
|
||||||
targets = active_browser_targets()
|
targets = active_browser_targets()
|
||||||
if len(targets) <= 1:
|
if len(targets) <= 1 and not any(target.remote for target in targets):
|
||||||
return []
|
return []
|
||||||
return targets
|
return targets
|
||||||
|
|
||||||
@@ -69,7 +71,7 @@ def windows_list():
|
|||||||
if targets:
|
if targets:
|
||||||
windows = []
|
windows = []
|
||||||
for target in targets:
|
for target in targets:
|
||||||
result = _handle_multi("windows.list", profile=target.profile)
|
result = _handle_multi("windows.list", profile=target.profile, remote=target.remote, token=target.token)
|
||||||
if result is None:
|
if result is None:
|
||||||
continue
|
continue
|
||||||
windows.extend({**window, "browser": target.display_name} for window in result)
|
windows.extend({**window, "browser": target.display_name} for window in result)
|
||||||
|
|||||||
+2
-2
@@ -17,9 +17,9 @@ def browser():
|
|||||||
"""Returns a connected send_command callable for the testing profile, or skips the test."""
|
"""Returns a connected send_command callable for the testing profile, or skips the test."""
|
||||||
try:
|
try:
|
||||||
send_command("tabs.list", profile=TEST_BROWSER_PROFILE)
|
send_command("tabs.list", profile=TEST_BROWSER_PROFILE)
|
||||||
except BrowserNotConnected:
|
except (BrowserNotConnected, RuntimeError) as e:
|
||||||
pytest.skip(
|
pytest.skip(
|
||||||
"Browser 'testing' not connected — start Brave/Chrome with the extension loaded for that profile"
|
f"Browser 'testing' not connected — start Brave/Chrome with the extension loaded for that profile ({e})"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _browser(command, args=None):
|
def _browser(command, args=None):
|
||||||
|
|||||||
+32
-1
@@ -149,7 +149,9 @@ def test_clients_remote_uses_remote_endpoint_without_local_registry():
|
|||||||
|
|
||||||
with patch.dict(os.environ, {}, clear=True), patch(
|
with patch.dict(os.environ, {}, clear=True), patch(
|
||||||
"browser_cli.cli.REGISTRY_PATH", Path("/nonexistent/browser-cli-registry.json")
|
"browser_cli.cli.REGISTRY_PATH", Path("/nonexistent/browser-cli-registry.json")
|
||||||
), patch("browser_cli.cli.send_command", side_effect=fake_send_command) as send_command:
|
), patch("browser_cli.cli.send_command", side_effect=fake_send_command) as send_command, patch(
|
||||||
|
"browser_cli.cli.save_remote_token"
|
||||||
|
):
|
||||||
result = CliRunner().invoke(main, ["--remote", "127.0.0.1:8765", "--token", "test", "clients"])
|
result = CliRunner().invoke(main, ["--remote", "127.0.0.1:8765", "--token", "test", "clients"])
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
@@ -551,3 +553,32 @@ def test_convert_html_to_markdown_indents_multiline_list_items():
|
|||||||
"- Unternehmensdaten → RAG → KI-Orchestrierung →\n"
|
"- Unternehmensdaten → RAG → KI-Orchestrierung →\n"
|
||||||
" Local LLMs / API Modelle / Spezialmodelle"
|
" Local LLMs / API Modelle / Spezialmodelle"
|
||||||
) in markdown
|
) in markdown
|
||||||
|
|
||||||
|
def test_remote_token_is_saved_when_passed_on_cli():
|
||||||
|
endpoint = "browser-host.example:8765"
|
||||||
|
with patch("browser_cli.cli.save_remote_token") as save_remote_token:
|
||||||
|
result = CliRunner().invoke(main, ["--remote", endpoint, "--token", "secret", "completion", "bash", "--script"])
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
save_remote_token.assert_called_once_with(endpoint, "secret")
|
||||||
|
|
||||||
|
|
||||||
|
def test_tabs_list_multi_browser_queries_remote_target():
|
||||||
|
endpoint = "browser-host.example:8765"
|
||||||
|
remote_target = BrowserTarget(
|
||||||
|
"work",
|
||||||
|
"browser-host.example:work",
|
||||||
|
"",
|
||||||
|
remote=endpoint,
|
||||||
|
token="secret",
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch("browser_cli.commands.tabs.active_browser_targets", return_value=[remote_target, BrowserTarget("local", "local", "/tmp/local.sock")]), patch(
|
||||||
|
"browser_cli.commands.tabs.send_command",
|
||||||
|
return_value=[{"id": 1, "windowId": 1, "active": True, "title": "Remote", "url": "https://example.com"}],
|
||||||
|
) as send_command:
|
||||||
|
result = CliRunner().invoke(main, ["tabs", "list"])
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
send_command.assert_any_call("tabs.list", {}, profile="work", remote=endpoint, token="secret")
|
||||||
|
assert "browser-host.example:work" in result.output
|
||||||
|
|||||||
+48
-3
@@ -3,7 +3,14 @@ from pathlib import Path
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from browser_cli.client import BrowserNotConnected, _resolve_socket, active_browser_targets, display_browser_name
|
from browser_cli.client import (
|
||||||
|
BrowserNotConnected,
|
||||||
|
_resolve_socket,
|
||||||
|
active_browser_targets,
|
||||||
|
display_browser_name,
|
||||||
|
save_remote_token,
|
||||||
|
token_for_remote,
|
||||||
|
)
|
||||||
from browser_cli.platform import endpoint_for_alias
|
from browser_cli.platform import endpoint_for_alias
|
||||||
|
|
||||||
def test_resolve_socket_raises_when_registry_missing(monkeypatch):
|
def test_resolve_socket_raises_when_registry_missing(monkeypatch):
|
||||||
@@ -63,7 +70,7 @@ def test_active_browser_targets_filters_stale_entries(monkeypatch, tmp_path):
|
|||||||
|
|
||||||
monkeypatch.setattr("browser_cli.client.REGISTRY_PATH", registry_path)
|
monkeypatch.setattr("browser_cli.client.REGISTRY_PATH", registry_path)
|
||||||
|
|
||||||
targets = active_browser_targets()
|
targets = active_browser_targets(include_remotes=False)
|
||||||
|
|
||||||
assert len(targets) == 1
|
assert len(targets) == 1
|
||||||
assert targets[0].profile == "work"
|
assert targets[0].profile == "work"
|
||||||
@@ -77,7 +84,45 @@ def test_active_browser_targets_keeps_windows_registry_entries(monkeypatch, tmp_
|
|||||||
monkeypatch.setattr("browser_cli.client.REGISTRY_PATH", registry_path)
|
monkeypatch.setattr("browser_cli.client.REGISTRY_PATH", registry_path)
|
||||||
monkeypatch.setattr("browser_cli.client.is_windows", lambda: True)
|
monkeypatch.setattr("browser_cli.client.is_windows", lambda: True)
|
||||||
|
|
||||||
targets = active_browser_targets()
|
targets = active_browser_targets(include_remotes=False)
|
||||||
|
|
||||||
assert len(targets) == 1
|
assert len(targets) == 1
|
||||||
assert targets[0].socket_path == r"\\.\pipe\browser-cli-work"
|
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_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"
|
||||||
|
|||||||
Reference in New Issue
Block a user