import json from pathlib import Path from unittest.mock import patch from click.testing import CliRunner import pytest from browser_cli import BrowserCLI from browser_cli.cli import main from browser_cli.command_security import CommandPolicy, assert_command_allowed, command_category def test_extension_info_cli_renders_capabilities(): with patch("browser_cli.send_command", return_value={"version": "1.2.3", "capabilities": ["extension.info"]}): result = CliRunner().invoke(main, ["extension", "info"]) assert result.exit_code == 0 assert "1.2.3" in result.output assert "extension.info" in result.output def test_script_runs_raw_commands(tmp_path: Path): script = tmp_path / "workflow.json" script.write_text(json.dumps([{"tabs.count": {"pattern": "example.com"}}]), encoding="utf-8") with patch("browser_cli.send_command", return_value={"count": 2}) as send_command: result = CliRunner().invoke(main, ["script", str(script), "--json"]) assert result.exit_code == 0 assert "tabs.count" in result.output send_command.assert_called_once_with("tabs.count", {"pattern": "example.com"}, profile=None, remote=None, key=None) def test_session_export_cli_prints_json(): with patch("browser_cli.send_command", return_value={"name": "work", "session": {"tabs": ["https://example.com"]}}): result = CliRunner().invoke(main, ["session", "export", "work"]) assert result.exit_code == 0 assert '"name": "work"' in result.output def test_nav_open_reuse_navigates_existing_tab_instead_of_opening_new(): calls = [] def sender(command, args=None, **kwargs): calls.append((command, args)) if command == "tabs.list": return [{"id": 7, "windowId": 1, "active": False, "muted": False, "title": "Example", "url": "https://example.com"}] return {} BrowserCLI(browser="testing", _command_sender=sender).nav.open("https://example.com", reuse=True) assert calls == [ ("tabs.list", {}), ("navigate.to", {"tabId": 7, "url": "https://example.com"}), ] def test_tabs_tree_command_available(): with patch("browser_cli.send_command", return_value=[]): result = CliRunner().invoke(main, ["tabs", "tree"]) assert result.exit_code == 0 assert "Tabs" in result.output def test_doctor_command_reports_connection_failure_cleanly(): with patch("browser_cli.commands.doctor.active_browser_targets", return_value=[]), \ patch("browser_cli.send_command", side_effect=RuntimeError("no browser")): result = CliRunner().invoke(main, ["doctor"]) assert result.exit_code == 1 assert "Connection" in result.output def test_serve_http_no_auth_rejected_on_public_host(): result = CliRunner().invoke(main, ["serve-http", "--host", "0.0.0.0", "--no-auth"]) assert result.exit_code != 0 assert "--no-auth is only allowed on loopback" in result.output def test_raw_command_blocks_dangerous_by_default(): result = CliRunner().invoke(main, ["command", "dom.eval", '{"code":"document.title"}']) assert result.exit_code != 0 assert "blocked by default" in result.output def test_raw_command_allows_dangerous_with_explicit_flag(): with patch("browser_cli.send_command", return_value="Example") as send_command: result = CliRunner().invoke(main, ["command", "--allow-dangerous", "dom.eval", '{"code":"document.title"}']) assert result.exit_code == 0 send_command.assert_called_once_with("dom.eval", {"code": "document.title"}, profile=None, remote=None, key=None) def test_script_blocks_control_without_explicit_flag(tmp_path: Path): script = tmp_path / "workflow.json" script.write_text(json.dumps([{"navigate.open": {"url": "https://example.com"}}]), encoding="utf-8") result = CliRunner().invoke(main, ["script", str(script), "--json"]) assert result.exit_code != 0 assert "blocked by default" in result.output def test_script_allows_control_with_explicit_flag(tmp_path: Path): script = tmp_path / "workflow.json" script.write_text(json.dumps([{"navigate.open": {"url": "https://example.com"}}]), encoding="utf-8") with patch("browser_cli.send_command", return_value={}) as send_command: result = CliRunner().invoke(main, ["script", str(script), "--json", "--allow-control"]) assert result.exit_code == 0 send_command.assert_called_once_with("navigate.open", {"url": "https://example.com"}, profile=None, remote=None, key=None) def test_command_policy_categories_and_flags(): assert command_category("tabs.list") == "safe" assert command_category("extract.text") == "read-page" assert command_category("dom.click") == "control" assert command_category("storage.get") == "dangerous" assert_command_allowed("tabs.list", CommandPolicy()) with pytest.raises(PermissionError): assert_command_allowed("extract.text", CommandPolicy()) assert_command_allowed("extract.text", CommandPolicy(allow_read_page=True)) with pytest.raises(PermissionError): assert_command_allowed("storage.get", CommandPolicy(allow_read_page=True, allow_control=True)) assert_command_allowed("storage.get", CommandPolicy(allow_dangerous=True))