实测 D:\DOWNLOADS\mail_bot.db 中 ChatGPT 推广邮件的退订链接 (https://r.openai.com/asm/unsubscribe/?...) 长度 700+ 字符, 而 _link_buttons 的 len(url) < 200 校验会把它过滤掉,导致 即使 AI 提取出退订链接、显示层也修好了,按钮依然不显示。 上限 200 -> 2048(Telegram inline URL 按钮安全范围)。 新增回归测试 test_long_unsub_url_rendered / test_oversized_url_filtered, 共 9 个用例全部通过。
105 lines
4.2 KiB
Python
105 lines
4.2 KiB
Python
"""测试摘要键盘渲染:推广邮件的退订链接按钮必须显示且排最前。"""
|
|
|
|
from src.tg_bot import _is_unsub_link, _link_buttons, _summary_keyboard
|
|
|
|
|
|
def _rows_of(kb: dict) -> list[list[dict]]:
|
|
return kb["inline_keyboard"]
|
|
|
|
|
|
def test_promotion_with_unsub_link_shows_button_first():
|
|
"""推广邮件:AI 提取到退订链接时必须显示,且排在第一行"""
|
|
links = [
|
|
{"text": "Manage Preferences", "url": "https://e.example.com/pref"},
|
|
{"text": "推广活动详情", "url": "https://e.example.com/deal"},
|
|
]
|
|
kb = _summary_keyboard(can_reply=False, is_promotion=True, links=links)
|
|
rows = _rows_of(kb)
|
|
first = rows[0][0]
|
|
assert first["url"] == "https://e.example.com/pref"
|
|
assert "🔕" in first["text"]
|
|
# 其余按钮仍然存在
|
|
texts = [b["text"] for r in rows for b in r]
|
|
assert "告诉我更多" in texts
|
|
assert "查看原文" in texts
|
|
|
|
|
|
def test_promotion_without_links_keeps_default_buttons():
|
|
"""推广邮件:没有链接时保持原有按钮"""
|
|
kb = _summary_keyboard(can_reply=False, is_promotion=True, links=None)
|
|
rows = _rows_of(kb)
|
|
assert len(rows) == 2
|
|
assert rows[0][0]["text"] == "告诉我更多"
|
|
assert rows[1][0]["text"] == "查看原文"
|
|
|
|
|
|
def test_promotion_with_only_non_unsub_link():
|
|
"""推广邮件:只有非退订链接时也正常渲染(🔗),不冒充退订按钮"""
|
|
links = [{"text": "查看活动", "url": "https://e.example.com/event"}]
|
|
kb = _summary_keyboard(can_reply=False, is_promotion=True, links=links)
|
|
rows = _rows_of(kb)
|
|
assert rows[0][0]["text"] == "🔗 查看活动"
|
|
assert rows[1][0]["text"] == "告诉我更多"
|
|
assert rows[2][0]["text"] == "查看原文"
|
|
|
|
|
|
def test_normal_email_links_still_rendered():
|
|
"""普通邮件:链接按钮行为不变"""
|
|
links = [{"text": "查看账单", "url": "https://e.example.com/bill"}]
|
|
kb = _summary_keyboard(can_reply=True, is_promotion=False, links=links)
|
|
rows = _rows_of(kb)
|
|
texts = [b["text"] for r in rows for b in r]
|
|
assert "🔗 查看账单" in texts
|
|
|
|
|
|
def test_unsub_detection_variants():
|
|
"""退订链接识别:文字与 URL 关键词变体"""
|
|
assert _is_unsub_link({"text": "Unsubscribe", "url": "https://x.com"})
|
|
assert _is_unsub_link({"text": "退订", "url": "https://x.com"})
|
|
assert _is_unsub_link({"text": "点击这里取消订阅", "url": "https://x.com"})
|
|
assert _is_unsub_link({"text": "管理偏好设置", "url": "https://x.com"})
|
|
assert _is_unsub_link({"text": "更多", "url": "https://x.com/unsubscribe"})
|
|
assert _is_unsub_link({"text": "Preferences", "url": "https://x.com/email-pref"})
|
|
assert not _is_unsub_link({"text": "查看详情", "url": "https://x.com/deal"})
|
|
|
|
|
|
def test_unsub_link_sorted_before_other_links():
|
|
"""退订链接即使不在 links 第一位也会被排到最前"""
|
|
links = [
|
|
{"text": "查看详情", "url": "https://x.com/a"},
|
|
{"text": "unsubscribe", "url": "https://x.com/b"},
|
|
]
|
|
rows = _link_buttons(links)
|
|
assert rows[0][0]["url"] == "https://x.com/b"
|
|
assert rows[1][0]["url"] == "https://x.com/a"
|
|
|
|
|
|
def test_invalid_url_filtered():
|
|
"""非法 URL(非 http、多重协议头)被过滤"""
|
|
links = [
|
|
{"text": "退订", "url": "javascript:alert(1)"},
|
|
{"text": "退订", "url": "https://x.com/https://evil.com"},
|
|
{"text": "退订", "url": "https://ok.example.com/unsub"},
|
|
]
|
|
rows = _link_buttons(links)
|
|
assert len(rows) == 1
|
|
assert rows[0][0]["url"] == "https://ok.example.com/unsub"
|
|
|
|
|
|
def test_long_unsub_url_rendered():
|
|
"""长 tracking 退订链接(如 OpenAI 的 >700 字符)必须渲染,不能被长度过滤误杀"""
|
|
url = "https://r.openai.com/asm/unsubscribe/?user_id=108370056&data=" + "x" * 700
|
|
assert len(url) > 200
|
|
links = [{"text": "取消订阅", "url": url}]
|
|
rows = _link_buttons(links)
|
|
assert len(rows) == 1
|
|
assert rows[0][0]["url"] == url
|
|
assert "🔕" in rows[0][0]["text"]
|
|
|
|
|
|
def test_oversized_url_filtered():
|
|
"""超过 2048 的 URL 仍被过滤"""
|
|
url = "https://example.com/" + "x" * 2100
|
|
links = [{"text": "退订", "url": url}]
|
|
rows = _link_buttons(links)
|
|
assert rows == [] |