Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions gateway/tests/telegram/test_telegram_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,24 @@ def test_send_message_success_with_mapping_proxy_data(mock_post: MagicMock) -> N
assert ok is True
assert error == ""
assert message_id == "42"


@patch("gateway.transports.telegram.poller.client.post_json")
def test_client_redacts_token_from_transport_error(mock_post: MagicMock) -> None:
token = "123456:SECRET"

mock_post.return_value = DeliveryResponse(
ok=False,
error=(f"ConnectError: https://api.telegram.org/bot{token}/sendMessage"),
exc_type="ConnectError",
)

client = TelegramBotClient(token)

ok, error, message_id = client.send_message("123", "hello")

assert ok is False
assert message_id == ""
assert token not in error
assert f"/bot{token}/" not in error
assert "<redacted>" in error
58 changes: 58 additions & 0 deletions gateway/tests/telegram/test_telegram_poller.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,61 @@ def test_poll_once_parses_callback_query(mock_get: MagicMock, mock_sleep: MagicM
# Ensure getUpdates asked for callback_query updates.
assert "callback_query" in mock_get.call_args.kwargs["params"]["allowed_updates"]
mock_sleep.assert_not_called()


@patch("gateway.transports.telegram.poller.poller.time.sleep")
@patch("gateway.transports.telegram.poller.poller.httpx.get")
def test_poll_once_redacts_token_from_transient_exception(
mock_get: MagicMock,
mock_sleep: MagicMock,
caplog: object,
) -> None:
import logging

token = "123456:SECRET"

mock_get.side_effect = httpx.ConnectError(
f"Connection failed for https://api.telegram.org/bot{token}/getUpdates"
)

caplog.set_level(
logging.DEBUG,
logger="gateway.transports.telegram.poller.poller",
)

result = TelegramPoller(token).poll_once()

assert result == TelegramPollResult()
assert token not in caplog.text
assert f"/bot{token}/" not in caplog.text
assert "<redacted>" in caplog.text
mock_sleep.assert_called_once_with(2.0)


@patch("gateway.transports.telegram.poller.poller.time.sleep")
@patch("gateway.transports.telegram.poller.poller.httpx.get")
def test_poll_once_redacts_token_from_error_response(
mock_get: MagicMock,
mock_sleep: MagicMock,
caplog: object,
) -> None:
import logging

token = "123456:SECRET"

mock_get.return_value = httpx.Response(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Hardcoded HTTP status code

The new response fixture uses the numeric status 500 instead of the repository-required named HTTPStatus constant, making the test less self-documenting and inconsistent with the established HTTP-status convention.

Context Used: AGENTS.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

500,
text=f"request failed: https://api.telegram.org/bot{token}/getUpdates",
)

caplog.set_level(
logging.DEBUG,
logger="gateway.transports.telegram.poller.poller",
)

TelegramPoller(token).poll_once()

assert token not in caplog.text
assert f"/bot{token}/" not in caplog.text
assert "<redacted>" in caplog.text
mock_sleep.assert_called_once_with(2.0)
3 changes: 2 additions & 1 deletion gateway/transports/telegram/poller/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import Any

from platform.notifications.delivery_transport import post_json
from platform.notifications.redaction import redact_token

logger = logging.getLogger(__name__)

Expand All @@ -25,7 +26,7 @@ def _call(self, method: str, payload: dict[str, Any]) -> tuple[bool, dict[str, A
payload=payload,
)
if not response.ok:
return False, {}, response.error
return False, {}, redact_token(response.error, self._token)
if response.status_code != 200 or not isinstance(response.data, Mapping):
return False, {}, response.text or f"HTTP {response.status_code}"
if not response.data.get("ok"):
Expand Down
13 changes: 10 additions & 3 deletions gateway/transports/telegram/poller/poller.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
TelegramCallbackQuery,
TelegramInboundMessage,
)
from platform.notifications.redaction import redact_token

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -65,7 +66,12 @@ def poll_once(self) -> TelegramPollResult:
try:
response = httpx.get(url, params=params, timeout=float(self._timeout + 5))
except Exception as exc:
self._log_transient("[telegram-gateway] getUpdates failed: %s", exc)
safe_error = redact_token(str(exc), self._token)
self._log_transient(
"[telegram-gateway] getUpdates failed (%s): %s",
type(exc).__name__,
safe_error,
)
time.sleep(_DEFAULT_RETRY_SECONDS)
return TelegramPollResult()

Expand Down Expand Up @@ -102,8 +108,9 @@ def _handle_poll_error(
response: httpx.Response,
) -> None:
error_code = data.get("error_code")
description = str(
data.get("description") or response.text.strip() or f"HTTP {response.status_code}"
description = redact_token(
str(data.get("description") or response.text.strip() or f"HTTP {response.status_code}"),
self._token,
)
if error_code == _CONFLICT_ERROR_CODE:
logger.debug(
Expand Down