Add "disable_web_page_preview": True to the JSON payload of both low-level Telegram primitives — send_telegram (POST /sendMessage) and edit_telegram (POST /editMessageText). Telegram no longer expands the Plane "Modern project management" link-preview banner under every tracker card (bump/edit) and notify/alert message, which the default bump mode (ORCH-067) was duplicating on each transition. Single-point fix at the primitive level — all consumers (update_task_tracker, notify_approve_requested, notify_error, stage alerts from launcher/stage_engine) inherit it without code changes. parse_mode: HTML is preserved so the ORCH-NNN issue link stays clickable; disable_notification, bump/edit logic, the one-card-per-task invariant, return contracts and never-raise are untouched. Unconditional, no kill-switch (ADR-001). Tests: tests/test_link_preview_disabled.py (TC-01..06). Docs: CHANGELOG, CLAUDE.md, docs/architecture/README.md (Notifications component). Refs: ORCH-080 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
160 lines
6.4 KiB
Python
160 lines
6.4 KiB
Python
"""ORCH-080 — suppress Telegram link-preview in tracker/notify primitives.
|
|
|
|
Both low-level primitives ``send_telegram`` (POST /sendMessage) and
|
|
``edit_telegram`` (POST /editMessageText) must add
|
|
``"disable_web_page_preview": True`` to their JSON payload, so the Plane
|
|
"Modern project management" banner no longer expands under every tracker card /
|
|
notification. The clickable issue link must stay clickable -> ``parse_mode:
|
|
"HTML"`` is preserved in both payloads, and the never-raise / return contracts
|
|
are unchanged.
|
|
|
|
Network is isolated: ``src.notifications.httpx`` is patched; creds are stubbed.
|
|
Test ids TC-01..TC-06 from 04-test-plan.yaml.
|
|
"""
|
|
|
|
import os
|
|
import tempfile
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
os.environ.setdefault("ORCH_PLANE_API_TOKEN", "test-token")
|
|
os.environ.setdefault("ORCH_GITEA_TOKEN", "test-token")
|
|
|
|
_test_db = os.path.join(tempfile.gettempdir(), "test_orchestrator_link_preview.db")
|
|
os.environ.setdefault("ORCH_DB_PATH", _test_db)
|
|
|
|
from src import notifications as N # noqa: E402
|
|
|
|
# conftest._no_telegram autouse-patches src.notifications.send_telegram to a
|
|
# no-op for every test (prod-leak guard). Capture the REAL implementation at
|
|
# import time (before any fixture runs) so these payload tests can exercise it.
|
|
_REAL_SEND = N.send_telegram
|
|
|
|
|
|
def _patch_tg_creds(monkeypatch):
|
|
monkeypatch.setattr(N._get_settings(), "telegram_bot_token", "T", raising=False)
|
|
monkeypatch.setattr(N._get_settings(), "telegram_chat_id", "C", raising=False)
|
|
|
|
|
|
def _ok_resp(message_id=42):
|
|
resp = MagicMock()
|
|
resp.json.return_value = {"ok": True, "result": {"message_id": message_id}}
|
|
return resp
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# TC-01 — send_telegram sets disable_web_page_preview: True
|
|
# --------------------------------------------------------------------------- #
|
|
def test_send_telegram_disables_link_preview(monkeypatch):
|
|
_patch_tg_creds(monkeypatch)
|
|
with patch("src.notifications.httpx") as hx:
|
|
hx.post.return_value = _ok_resp()
|
|
_REAL_SEND("hello")
|
|
payload = hx.post.call_args.kwargs["json"]
|
|
assert payload["disable_web_page_preview"] is True
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# TC-02 — edit_telegram sets disable_web_page_preview: True
|
|
# --------------------------------------------------------------------------- #
|
|
def test_edit_telegram_disables_link_preview(monkeypatch):
|
|
_patch_tg_creds(monkeypatch)
|
|
with patch("src.notifications.httpx") as hx:
|
|
hx.post.return_value = _ok_resp()
|
|
N.edit_telegram(1, "hello")
|
|
payload = hx.post.call_args.kwargs["json"]
|
|
assert payload["disable_web_page_preview"] is True
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# TC-03 — parse_mode HTML preserved in both payloads (clickable <a href>)
|
|
# --------------------------------------------------------------------------- #
|
|
def test_send_telegram_keeps_parse_mode_html(monkeypatch):
|
|
_patch_tg_creds(monkeypatch)
|
|
with patch("src.notifications.httpx") as hx:
|
|
hx.post.return_value = _ok_resp()
|
|
_REAL_SEND("hello")
|
|
assert hx.post.call_args.kwargs["json"]["parse_mode"] == "HTML"
|
|
|
|
|
|
def test_edit_telegram_keeps_parse_mode_html(monkeypatch):
|
|
_patch_tg_creds(monkeypatch)
|
|
with patch("src.notifications.httpx") as hx:
|
|
hx.post.return_value = _ok_resp()
|
|
N.edit_telegram(1, "hello")
|
|
assert hx.post.call_args.kwargs["json"]["parse_mode"] == "HTML"
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# TC-04 — send_telegram preserves existing fields + disable_notification arg
|
|
# --------------------------------------------------------------------------- #
|
|
def test_send_telegram_preserves_existing_fields(monkeypatch):
|
|
_patch_tg_creds(monkeypatch)
|
|
with patch("src.notifications.httpx") as hx:
|
|
hx.post.return_value = _ok_resp()
|
|
_REAL_SEND("body", disable_notification=True)
|
|
payload = hx.post.call_args.kwargs["json"]
|
|
assert payload["chat_id"] == "C"
|
|
assert payload["text"] == "body"
|
|
assert payload["parse_mode"] == "HTML"
|
|
assert payload["disable_notification"] is True
|
|
|
|
|
|
def test_send_telegram_disable_notification_default_false(monkeypatch):
|
|
_patch_tg_creds(monkeypatch)
|
|
with patch("src.notifications.httpx") as hx:
|
|
hx.post.return_value = _ok_resp()
|
|
_REAL_SEND("body")
|
|
assert hx.post.call_args.kwargs["json"]["disable_notification"] is False
|
|
|
|
|
|
def test_edit_telegram_preserves_existing_fields(monkeypatch):
|
|
_patch_tg_creds(monkeypatch)
|
|
with patch("src.notifications.httpx") as hx:
|
|
hx.post.return_value = _ok_resp()
|
|
N.edit_telegram(7, "body")
|
|
payload = hx.post.call_args.kwargs["json"]
|
|
assert payload["chat_id"] == "C"
|
|
assert payload["message_id"] == 7
|
|
assert payload["text"] == "body"
|
|
assert payload["parse_mode"] == "HTML"
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# TC-05 — return contracts unchanged
|
|
# --------------------------------------------------------------------------- #
|
|
def test_send_telegram_returns_message_id(monkeypatch):
|
|
_patch_tg_creds(monkeypatch)
|
|
with patch("src.notifications.httpx") as hx:
|
|
hx.post.return_value = _ok_resp(message_id=99)
|
|
assert _REAL_SEND("x") == 99
|
|
|
|
|
|
def test_send_telegram_returns_none_without_creds(monkeypatch):
|
|
monkeypatch.setattr(N._get_settings(), "telegram_bot_token", "", raising=False)
|
|
monkeypatch.setattr(N._get_settings(), "telegram_chat_id", "", raising=False)
|
|
assert _REAL_SEND("x") is None
|
|
|
|
|
|
def test_edit_telegram_returns_edit_ok(monkeypatch):
|
|
_patch_tg_creds(monkeypatch)
|
|
with patch("src.notifications.httpx") as hx:
|
|
hx.post.return_value = _ok_resp()
|
|
assert N.edit_telegram(1, "x") == N.EDIT_OK
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# TC-06 — never-raise: httpx.post raising -> None / EDIT_FAILED
|
|
# --------------------------------------------------------------------------- #
|
|
def test_send_telegram_never_raises(monkeypatch):
|
|
_patch_tg_creds(monkeypatch)
|
|
with patch("src.notifications.httpx") as hx:
|
|
hx.post.side_effect = Exception("boom")
|
|
assert _REAL_SEND("x") is None
|
|
|
|
|
|
def test_edit_telegram_never_raises(monkeypatch):
|
|
_patch_tg_creds(monkeypatch)
|
|
with patch("src.notifications.httpx") as hx:
|
|
hx.post.side_effect = Exception("boom")
|
|
assert N.edit_telegram(1, "x") == N.EDIT_FAILED
|