diff --git a/nanobot/channels/feishu.py b/nanobot/channels/feishu.py index 4f42fdba8..65589ec2e 100644 --- a/nanobot/channels/feishu.py +++ b/nanobot/channels/feishu.py @@ -371,6 +371,8 @@ def _begin_registration(domain: str = "feishu") -> dict: if not device_code: raise RuntimeError("Feishu / Lark registration did not return a device_code") qr_url = res.get("verification_uri_complete", "") + if not qr_url: + raise RuntimeError("Feishu / Lark registration did not return a login URL") if "?" in qr_url: qr_url += "&from=nanobot&tp=nanobot" else: @@ -715,7 +717,7 @@ class FeishuChannel(BaseChannel): print(f"Domain: {self.config.domain}") # Write credentials back to config.json - from nanobot.config.loader import load_config, save_config + from nanobot.config.loader import get_config_path, load_config, save_config full_config = load_config() feishu_cfg = getattr(full_config.channels, "feishu", None) or {} @@ -727,7 +729,7 @@ class FeishuChannel(BaseChannel): setattr(full_config.channels, "feishu", feishu_cfg) save_config(full_config) - print(f"\nCredentials saved to ~/.nanobot/config.json (feishu enabled)") + print(f"\nCredentials saved to {get_config_path()} (feishu enabled)") print("Login successful!") return True diff --git a/tests/channels/test_feishu_login.py b/tests/channels/test_feishu_login.py new file mode 100644 index 000000000..ec6bceaa2 --- /dev/null +++ b/tests/channels/test_feishu_login.py @@ -0,0 +1,60 @@ +import json + +import pytest +from typer.testing import CliRunner + +from nanobot.channels import feishu as feishu_module +from nanobot.channels.feishu import FeishuChannel +from nanobot.cli.commands import app +from nanobot.config import loader +from nanobot.config.schema import Config + + +@pytest.mark.asyncio +async def test_feishu_login_writes_credentials_to_active_config(monkeypatch, tmp_path): + config_path = tmp_path / "config.json" + config = Config() + config.channels.feishu = {"enabled": False, "domain": "feishu"} + loader.save_config(config, config_path) + monkeypatch.setattr(loader, "_current_config_path", config_path) + monkeypatch.setattr( + feishu_module, + "qr_register", + lambda initial_domain="feishu": { + "app_id": "cli_app", + "app_secret": "secret", + "domain": "lark", + "bot_name": None, + "bot_open_id": None, + }, + ) + + channel = FeishuChannel({"enabled": False, "domain": "feishu"}, None) + + assert await channel.login() is True + data = json.loads(config_path.read_text(encoding="utf-8")) + assert data["channels"]["feishu"]["appId"] == "cli_app" + assert data["channels"]["feishu"]["appSecret"] == "secret" + assert data["channels"]["feishu"]["domain"] == "lark" + assert data["channels"]["feishu"]["enabled"] is True + + +def test_begin_registration_requires_login_url(monkeypatch): + monkeypatch.setattr( + feishu_module, + "_post_registration", + lambda _base_url, _body: {"device_code": "device"}, + ) + + with pytest.raises(RuntimeError, match="login URL"): + feishu_module._begin_registration() + + +def test_channels_login_feishu_requires_default_config_file(monkeypatch, tmp_path): + missing_config = tmp_path / "missing.json" + monkeypatch.setattr(loader, "get_config_path", lambda: missing_config) + + result = CliRunner().invoke(app, ["channels", "login", "feishu"]) + + assert result.exit_code == 1 + assert "No configuration file found" in result.output