import json import logging from pathlib import Path from typer.testing import CliRunner from ohmo.cli import _build_gateway_logging_handlers, app def test_ohmo_help(): runner = CliRunner() result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "personal-agent app" in result.output assert "config" in result.output def test_ohmo_init_and_doctor(tmp_path: Path): runner = CliRunner() workspace = tmp_path / ".ohmo-home" result = runner.invoke(app, ["init", "--workspace", str(workspace), "--no-interactive"]) assert result.exit_code == 0 assert str(workspace) in result.output doctor = runner.invoke(app, ["doctor", "--cwd", str(tmp_path), "--workspace", str(workspace)]) assert doctor.exit_code == 0 assert "ohmo doctor:" in doctor.output assert "workspace: ok" in doctor.output def test_ohmo_init_existing_workspace_points_to_config(tmp_path: Path): runner = CliRunner() workspace = tmp_path / ".ohmo-home" first = runner.invoke(app, ["init", "--workspace", str(workspace), "--no-interactive"]) assert first.exit_code == 0 second = runner.invoke(app, ["init", "--workspace", str(workspace), "--no-interactive"]) assert second.exit_code == 0 assert "ohmo workspace already exists." in second.output assert "Use `ohmo config`" in second.output def test_ohmo_init_noninteractive_defaults_to_deny_all_remote_access(tmp_path: Path): runner = CliRunner() workspace = tmp_path / ".ohmo-home" result = runner.invoke(app, ["init", "--workspace", str(workspace), "--no-interactive"]) assert result.exit_code == 0 config = json.loads((workspace / "gateway.json").read_text(encoding="utf-8")) assert config["channel_configs"] == {} def test_gateway_logging_handlers_write_gateway_log_file(tmp_path: Path): workspace = tmp_path / ".ohmo-home" runner = CliRunner() result = runner.invoke(app, ["init", "--workspace", str(workspace), "--no-interactive"]) assert result.exit_code == 0 handlers = _build_gateway_logging_handlers(workspace, console=True, log_file=True) try: file_handlers = [handler for handler in handlers if isinstance(handler, logging.FileHandler)] console_handlers = [ handler for handler in handlers if isinstance(handler, logging.StreamHandler) and not isinstance(handler, logging.FileHandler) ] assert len(file_handlers) == 1 assert len(console_handlers) == 1 record = logging.LogRecord( name="ohmo.gateway.test", level=logging.INFO, pathname=__file__, lineno=1, msg="GATEWAY_LOG_OK", args=(), exc_info=None, ) file_handlers[0].emit(record) file_handlers[0].flush() log_path = workspace / "logs" / "gateway.log" assert "GATEWAY_LOG_OK" in log_path.read_text(encoding="utf-8") finally: for handler in handlers: handler.close() def test_ohmo_init_interactive_writes_gateway_config(tmp_path: Path, monkeypatch): runner = CliRunner() workspace = tmp_path / ".ohmo-home" monkeypatch.setattr("sys.stdin.isatty", lambda: True) monkeypatch.setattr("sys.stdout.isatty", lambda: True) user_input = "\n".join( [ "1", # provider profile "y", # enable telegram "123456", # allow_from "telegram-token", "y", # reply_to_message "n", # slack "n", # discord "n", # feishu "y", # send_progress "y", # send_tool_hints "n", # allow_remote_admin_commands ] ) result = runner.invoke(app, ["init", "--workspace", str(workspace)], input=user_input) assert result.exit_code == 0 config = json.loads((workspace / "gateway.json").read_text(encoding="utf-8")) assert config["enabled_channels"] == ["telegram"] assert config["channel_configs"]["telegram"]["token"] == "telegram-token" assert config["channel_configs"]["telegram"]["allow_from"] == ["123456"] def test_ohmo_init_interactive_allows_blank_allow_from_for_secure_default(tmp_path: Path, monkeypatch): runner = CliRunner() workspace = tmp_path / ".ohmo-home" monkeypatch.setattr("sys.stdin.isatty", lambda: True) monkeypatch.setattr("sys.stdout.isatty", lambda: True) user_input = "\n".join( [ "1", # provider profile "y", # enable telegram "", # allow_from -> deny all until explicitly configured "telegram-token", "y", # reply_to_message "n", # slack "n", # discord "n", # feishu "y", # send_progress "y", # send_tool_hints "n", # allow_remote_admin_commands ] ) result = runner.invoke(app, ["init", "--workspace", str(workspace)], input=user_input) assert result.exit_code == 0 config = json.loads((workspace / "gateway.json").read_text(encoding="utf-8")) assert config["channel_configs"]["telegram"]["allow_from"] == [] assert "Remote access denied until allow_from is configured for: telegram" in result.output def test_ohmo_init_interactive_writes_feishu_gateway_config(tmp_path: Path, monkeypatch): runner = CliRunner() workspace = tmp_path / ".ohmo-home" monkeypatch.setattr("sys.stdin.isatty", lambda: True) monkeypatch.setattr("sys.stdout.isatty", lambda: True) user_input = "\n".join( [ "1", # provider profile "n", # telegram "n", # slack "n", # discord "y", # feishu "feishu-user-1", # allow_from "1", # domain -> Feishu (China) "cli_app", # app_id "cli_secret",# app_secret "enc_key", # encrypt_key "verify_me", # verification_token "OK", # react_emoji "1", # group_policy -> managed_or_mention "ohmo,openclaw", # bot_names "", # bot_open_id "y", # send_progress "n", # send_tool_hints "n", # allow_remote_admin_commands ] ) result = runner.invoke(app, ["init", "--workspace", str(workspace)], input=user_input) assert result.exit_code == 0 config = json.loads((workspace / "gateway.json").read_text(encoding="utf-8")) assert config["enabled_channels"] == ["feishu"] assert config["channel_configs"]["feishu"]["domain"] == "https://open.feishu.cn" assert config["channel_configs"]["feishu"]["app_id"] == "cli_app" assert config["channel_configs"]["feishu"]["app_secret"] == "cli_secret" assert config["channel_configs"]["feishu"]["encrypt_key"] == "enc_key" assert config["channel_configs"]["feishu"]["verification_token"] == "verify_me" assert config["channel_configs"]["feishu"]["react_emoji"] == "OK" assert config["channel_configs"]["feishu"]["group_policy"] == "managed_or_mention" assert config["channel_configs"]["feishu"]["bot_names"] == ["ohmo", "openclaw"] def test_ohmo_config_interactive_can_restart_gateway(tmp_path: Path, monkeypatch): runner = CliRunner() workspace = tmp_path / ".ohmo-home" runner.invoke(app, ["init", "--workspace", str(workspace), "--no-interactive"]) monkeypatch.setattr("sys.stdin.isatty", lambda: True) monkeypatch.setattr("sys.stdout.isatty", lambda: True) monkeypatch.setattr("ohmo.cli.gateway_status", lambda cwd, workspace: type("State", (), {"running": True})()) monkeypatch.setattr("ohmo.cli.stop_gateway_process", lambda cwd, workspace: True) monkeypatch.setattr("ohmo.cli.start_gateway_process", lambda cwd, workspace: 4321) user_input = "\n".join( [ "4", # provider profile -> codex "n", # telegram "n", # slack "n", # discord "y", # feishu "feishu-user-1", # allow_from "2", # domain -> Lark (International) "cli_app", # app_id "cli_secret", # app_secret "", # encrypt_key "verify_me", # verification_token "OK", # react_emoji "1", # group_policy -> managed_or_mention "ohmo,openclaw", # bot_names "", # bot_open_id "y", # send_progress "y", # send_tool_hints "n", # allow_remote_admin_commands "y", # restart gateway ] ) result = runner.invoke(app, ["config", "--workspace", str(workspace)], input=user_input) assert result.exit_code == 0 assert "ohmo gateway restarted (pid=4321)" in result.output config = json.loads((workspace / "gateway.json").read_text(encoding="utf-8")) assert config["provider_profile"] == "codex" assert config["enabled_channels"] == ["feishu"] assert config["channel_configs"]["feishu"]["domain"] == "https://open.larksuite.com" def test_ohmo_config_keeps_existing_channel_when_not_reconfigured(tmp_path: Path, monkeypatch): runner = CliRunner() workspace = tmp_path / ".ohmo-home" runner.invoke(app, ["init", "--workspace", str(workspace), "--no-interactive"]) gateway_path = workspace / "gateway.json" config = json.loads(gateway_path.read_text(encoding="utf-8")) config["enabled_channels"] = ["feishu"] config["channel_configs"]["feishu"] = { "allow_from": ["feishu-user-1"], "app_id": "old_app", "app_secret": "old_secret", "encrypt_key": "", "verification_token": "old_verify", "react_emoji": "OK", } gateway_path.write_text(json.dumps(config, indent=2) + "\n", encoding="utf-8") monkeypatch.setattr("sys.stdin.isatty", lambda: True) monkeypatch.setattr("sys.stdout.isatty", lambda: True) monkeypatch.setattr("ohmo.cli.gateway_status", lambda cwd, workspace: type("State", (), {"running": False})()) user_input = "\n".join( [ "4", # provider profile -> codex "n", # telegram "n", # slack "n", # discord "n", # reconfigure feishu? keep existing "y", # send_progress "y", # send_tool_hints "n", # allow_remote_admin_commands ] ) result = runner.invoke(app, ["config", "--workspace", str(workspace)], input=user_input) assert result.exit_code == 0 updated = json.loads(gateway_path.read_text(encoding="utf-8")) assert updated["enabled_channels"] == ["feishu"] assert updated["channel_configs"]["feishu"]["app_id"] == "old_app" assert updated["channel_configs"]["feishu"]["app_secret"] == "old_secret"