2026-06-18 13:35:17 +08:00
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
|
2026-06-18 14:00:22 +08:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_feishu_login_creates_missing_active_config(monkeypatch, tmp_path):
|
2026-06-18 13:35:17 +08:00
|
|
|
missing_config = tmp_path / "missing.json"
|
2026-06-18 14:00:22 +08:00
|
|
|
monkeypatch.setattr(loader, "_current_config_path", missing_config)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
feishu_module,
|
|
|
|
|
"qr_register",
|
|
|
|
|
lambda initial_domain="feishu": {
|
|
|
|
|
"app_id": "cli_app",
|
|
|
|
|
"app_secret": "secret",
|
|
|
|
|
"domain": "feishu",
|
|
|
|
|
"bot_name": None,
|
|
|
|
|
"bot_open_id": None,
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-06-18 13:35:17 +08:00
|
|
|
|
2026-06-18 14:00:22 +08:00
|
|
|
channel = FeishuChannel({}, None)
|
2026-06-18 13:35:17 +08:00
|
|
|
|
2026-06-18 14:00:22 +08:00
|
|
|
assert await channel.login() is True
|
|
|
|
|
assert missing_config.exists()
|
|
|
|
|
data = json.loads(missing_config.read_text(encoding="utf-8"))
|
|
|
|
|
assert data["channels"]["feishu"]["appId"] == "cli_app"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_channels_login_feishu_uses_generic_channel_login(monkeypatch, tmp_path):
|
|
|
|
|
missing_config = tmp_path / "missing.json"
|
|
|
|
|
seen: dict[str, object] = {}
|
|
|
|
|
|
|
|
|
|
class _LoginChannel:
|
|
|
|
|
display_name = "Feishu"
|
|
|
|
|
|
|
|
|
|
def __init__(self, config, bus):
|
|
|
|
|
seen["config"] = config
|
|
|
|
|
seen["bus"] = bus
|
|
|
|
|
|
|
|
|
|
async def login(self, force: bool = False) -> bool:
|
|
|
|
|
seen["force"] = force
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(loader, "_current_config_path", missing_config)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"nanobot.channels.registry.discover_all",
|
|
|
|
|
lambda: {"feishu": _LoginChannel},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
result = CliRunner().invoke(app, ["channels", "login", "feishu", "--force"])
|
|
|
|
|
|
|
|
|
|
assert result.exit_code == 0
|
|
|
|
|
assert seen["force"] is True
|