From 199a1bb8fa4a73066a1994de462b66091c2c40db Mon Sep 17 00:00:00 2001 From: chengyongru Date: Fri, 15 May 2026 10:31:29 +0800 Subject: [PATCH] =?UTF-8?q?docs(pairing):=20address=20reviewer=20comments?= =?UTF-8?q?=20=E2=80=94=20comments,=20error=20msg,=20=5F=5Fall=5F=5F=20tes?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Clarify SystemExit message for missing/null allowFrom (manager.py) - Document why Feishu passes content="" for unauthorized DMs - Document exact-match semantics in BaseChannel.is_allowed() - Document negligible collision probability in generate_code() - Add test_all_exports_are_importable for nanobot.pairing.__all__ --- nanobot/channels/base.py | 1 + nanobot/channels/feishu.py | 2 ++ nanobot/channels/manager.py | 2 +- nanobot/pairing/store.py | 2 ++ tests/pairing/test_store.py | 9 +++++++++ 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/nanobot/channels/base.py b/nanobot/channels/base.py index ed5c54232..a578e9971 100644 --- a/nanobot/channels/base.py +++ b/nanobot/channels/base.py @@ -202,6 +202,7 @@ class BaseChannel(ABC): allow_list = getattr(self.config, "allow_from", []) or [] if "*" in allow_list: return True + # allowFrom entries are opaque tokens — must match exactly. if str(sender_id) in allow_list: return True if is_approved(self.name, str(sender_id)): diff --git a/nanobot/channels/feishu.py b/nanobot/channels/feishu.py index 5a6b32885..c5e085972 100644 --- a/nanobot/channels/feishu.py +++ b/nanobot/channels/feishu.py @@ -1716,6 +1716,8 @@ class FeishuChannel(BaseChannel): # Group chats are silently ignored; DMs get a pairing code. if not self.is_allowed(sender_id): if chat_type == "p2p": + # content="" because the pairing reply is generated by + # BaseChannel._handle_message, not from the original message. await self._handle_message( sender_id=sender_id, chat_id=sender_id, diff --git a/nanobot/channels/manager.py b/nanobot/channels/manager.py index de0ed0c01..b63510fae 100644 --- a/nanobot/channels/manager.py +++ b/nanobot/channels/manager.py @@ -145,7 +145,7 @@ class ChannelManager: allow = getattr(cfg, "allow_from", None) if allow is None: raise SystemExit( - f'Error: "{name}" is missing allowFrom. ' + f'Error: "{name}" is missing or null allowFrom. ' f'Set ["*"] to allow everyone, or add specific user IDs.' ) diff --git a/nanobot/pairing/store.py b/nanobot/pairing/store.py index 340a85b3b..78e041612 100644 --- a/nanobot/pairing/store.py +++ b/nanobot/pairing/store.py @@ -82,6 +82,8 @@ def generate_code( with _LOCK: data = _load() _gc_pending(data) + # Collision probability is negligible (~1e-12 with 20 pending codes), + # so we skip an existence check for simplicity. raw = "".join(secrets.choice(_ALPHABET) for _ in range(_CODE_LENGTH)) code = f"{raw[:4]}-{raw[4:]}" diff --git a/tests/pairing/test_store.py b/tests/pairing/test_store.py index 1c06cc554..25c8ec7c7 100644 --- a/tests/pairing/test_store.py +++ b/tests/pairing/test_store.py @@ -2,9 +2,18 @@ import time import pytest +from nanobot.pairing import __all__ as pairing_all from nanobot.pairing import store +def test_all_exports_are_importable(): + """Every name in __all__ must actually be importable from nanobot.pairing.""" + import nanobot.pairing as pkg + + for name in pairing_all: + assert hasattr(pkg, name), f"{name} is in __all__ but not exported" + + @pytest.fixture(autouse=True) def _tmp_store(tmp_path, monkeypatch): path = tmp_path / "pairing.json"