diff --git a/plugins/platforms/telegram/adapter.py b/plugins/platforms/telegram/adapter.py index ea6258b9f2dc..32be4d2bb306 100644 --- a/plugins/platforms/telegram/adapter.py +++ b/plugins/platforms/telegram/adapter.py @@ -2161,7 +2161,9 @@ def _record_polling_progress(self, generation: int) -> None: return self._polling_progress_event.set() self._polling_network_error_count = 0 - if generation == self._polling_conflict_recovery_generation: + # Bare/test adapters may not have run ``__init__``; treat missing as + # "no conflict recovery in flight" (same defensive shape as teardown). + if generation == getattr(self, "_polling_conflict_recovery_generation", None): self._polling_conflict_recovery_generation = None else: self._polling_conflict_count = 0 diff --git a/tests/tools/test_discord_tool.py b/tests/tools/test_discord_tool.py index 8de83382abe2..15c7e9ce1166 100644 --- a/tests/tools/test_discord_tool.py +++ b/tests/tools/test_discord_tool.py @@ -249,6 +249,15 @@ def test_search_members_limit_capped(self, mock_req, monkeypatch): call_params = mock_req.call_args[1]["params"] assert call_params["limit"] == "100" # Capped at 100 + @patch("tools.discord_tool._discord_request") + def test_search_members_limit_floored(self, mock_req, monkeypatch): + monkeypatch.setenv("DISCORD_BOT_TOKEN", "test-token") + mock_req.return_value = [] + discord_core(action="search_members", guild_id="111", query="x", limit=-5) + assert mock_req.call_args[1]["params"]["limit"] == "1" + discord_core(action="search_members", guild_id="111", query="x", limit=0) + assert mock_req.call_args[1]["params"]["limit"] == "1" + # --------------------------------------------------------------------------- # Action: fetch_messages @@ -274,6 +283,17 @@ def test_fetch_messages(self, mock_req, monkeypatch): assert result["messages"][0]["content"] == "Hello world" assert result["messages"][0]["author"]["username"] == "user1" + @patch("tools.discord_tool._discord_request") + def test_fetch_messages_limit_clamped(self, mock_req, monkeypatch): + monkeypatch.setenv("DISCORD_BOT_TOKEN", "test-token") + mock_req.return_value = [] + discord_core(action="fetch_messages", channel_id="11", limit=500) + assert mock_req.call_args[1]["params"]["limit"] == "100" + discord_core(action="fetch_messages", channel_id="11", limit=-3) + assert mock_req.call_args[1]["params"]["limit"] == "1" + discord_core(action="fetch_messages", channel_id="11", limit=0) + assert mock_req.call_args[1]["params"]["limit"] == "1" + # --------------------------------------------------------------------------- # Action: create_thread diff --git a/tools/discord_tool.py b/tools/discord_tool.py index db979a21d87d..df5c9d236c1f 100644 --- a/tools/discord_tool.py +++ b/tools/discord_tool.py @@ -491,7 +491,10 @@ def _search_members(token: str, guild_id: str, query: str, limit: int = 20, **_k limit = int(limit) except (TypeError, ValueError): limit = 20 - params = {"query": query, "limit": str(min(limit, 100))} + # Discord members/search requires limit in [1, 100]. Upper-only min() + # still forwards 0/negatives which the API rejects with 400. + limit = max(1, min(limit, 100)) + params = {"query": query, "limit": str(limit)} members = _discord_request("GET", f"/guilds/{guild_id}/members/search", token, params=params) result = [] for m in members: @@ -517,7 +520,9 @@ def _fetch_messages( limit = int(limit) except (TypeError, ValueError): limit = 50 - params: Dict[str, str] = {"limit": str(min(limit, 100))} + # Discord channel message list requires limit in [1, 100]. + limit = max(1, min(limit, 100)) + params: Dict[str, str] = {"limit": str(limit)} if before: params["before"] = before if after: