af229e2a7b
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
213 lines
6.9 KiB
Python
213 lines
6.9 KiB
Python
"""AlertHit[] → 飞书消息:分组 / 截断 / 含关键词(text + post 两种格式)。"""
|
||
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
|
||
from app.services.compare_alert import AlertHit
|
||
from app.services.compare_alert_format import ALERT_KEYWORD, format_alert_message, format_alert_post
|
||
|
||
|
||
def _hits(n, alert_type="T1"):
|
||
return [
|
||
AlertHit(
|
||
trace_id=f"t{i}",
|
||
alert_type=alert_type,
|
||
reason="比价过程出错",
|
||
app_version="v0.3.4",
|
||
created_at=None,
|
||
trace_url=None,
|
||
user_id=None,
|
||
)
|
||
for i in range(n)
|
||
]
|
||
|
||
|
||
# ---- format_alert_message(纯文本,保留原有测试) ----
|
||
|
||
def test_contains_keyword_and_count():
|
||
msg = format_alert_message(
|
||
_hits(2), window_label="2026-08-04 08:00–08:30",
|
||
max_detail_per_type=20, max_total=50,
|
||
)
|
||
assert ALERT_KEYWORD in msg
|
||
assert "本期触发 2 条" in msg
|
||
assert "系统技术失败 2 条" in msg
|
||
assert "t0" in msg and "v0.3.4" in msg
|
||
|
||
|
||
def test_group_by_type():
|
||
hits = _hits(1, "T1") + _hits(1, "T6") + _hits(1, "T5")
|
||
msg = format_alert_message(hits, window_label="w", max_detail_per_type=20, max_total=50)
|
||
assert "系统技术失败 1 条" in msg
|
||
assert "商品识别失败 1 条" in msg
|
||
assert "深度放弃(cancelled) 1 条" in msg
|
||
|
||
|
||
def test_per_type_truncation():
|
||
msg = format_alert_message(_hits(25), window_label="w", max_detail_per_type=20, max_total=50)
|
||
assert msg.count("t0") == 1
|
||
assert "另有 5 条" in msg
|
||
|
||
|
||
def test_total_truncation_counts_only():
|
||
msg = format_alert_message(_hits(60), window_label="w", max_detail_per_type=20, max_total=50)
|
||
assert "系统技术失败 60 条" in msg
|
||
assert "t0" not in msg
|
||
assert "分析库" in msg
|
||
|
||
|
||
# ---- format_alert_post(富文本 post) ----
|
||
|
||
def _hits_with_meta(n, alert_type="T1", *, user_id=None, trace_url=None, created_at=None):
|
||
return [
|
||
AlertHit(
|
||
trace_id=f"tr{i}",
|
||
alert_type=alert_type,
|
||
reason="比价过程出错",
|
||
app_version="v1.2.3",
|
||
created_at=created_at or datetime(2026, 8, 4, 10, 30),
|
||
trace_url=trace_url,
|
||
user_id=user_id,
|
||
)
|
||
for i in range(n)
|
||
]
|
||
|
||
|
||
def test_post_title_contains_keyword():
|
||
hits = _hits_with_meta(1)
|
||
title, content = format_alert_post(
|
||
hits, window_label="2026-08-04 10:00", phone_map={}, max_detail_per_type=20, max_total=50,
|
||
)
|
||
assert ALERT_KEYWORD in title
|
||
|
||
|
||
def test_post_content_is_list():
|
||
hits = _hits_with_meta(2)
|
||
title, content = format_alert_post(
|
||
hits, window_label="w", phone_map={}, max_detail_per_type=20, max_total=50,
|
||
)
|
||
assert isinstance(content, list)
|
||
assert len(content) >= 1
|
||
# 每个段落是 list[dict]
|
||
for para in content:
|
||
assert isinstance(para, list)
|
||
for elem in para:
|
||
assert "tag" in elem
|
||
|
||
|
||
def test_post_summary_count():
|
||
hits = _hits_with_meta(3, "T1") + _hits_with_meta(2, "T6")
|
||
title, content = format_alert_post(
|
||
hits, window_label="w", phone_map={}, max_detail_per_type=20, max_total=50,
|
||
)
|
||
# 摘要第一段含合计数和类型计数
|
||
first_para_text = "".join(e.get("text", "") for e in content[0])
|
||
assert "合计 5 条" in first_para_text
|
||
assert "系统技术失败 3" in first_para_text
|
||
assert "商品识别失败 2" in first_para_text
|
||
|
||
|
||
def test_post_phone_map_applied():
|
||
hits = _hits_with_meta(1, user_id=42)
|
||
title, content = format_alert_post(
|
||
hits, window_label="w", phone_map={42: "13800138000"}, max_detail_per_type=20, max_total=50,
|
||
)
|
||
# 找明细行(非摘要非表头)中含手机号
|
||
all_text = " ".join(
|
||
e.get("text", "") for para in content for e in para
|
||
)
|
||
assert "13800138000" in all_text
|
||
|
||
|
||
def test_post_no_user_id_shows_dash():
|
||
hits = _hits_with_meta(1, user_id=None)
|
||
title, content = format_alert_post(
|
||
hits, window_label="w", phone_map={}, max_detail_per_type=20, max_total=50,
|
||
)
|
||
all_text = " ".join(e.get("text", "") for para in content for e in para)
|
||
assert "| - |" in all_text
|
||
|
||
|
||
def test_post_trace_url_becomes_a_element():
|
||
hits = _hits_with_meta(1, trace_url="https://trace.example.com/tr0")
|
||
title, content = format_alert_post(
|
||
hits, window_label="w", phone_map={}, max_detail_per_type=20, max_total=50,
|
||
)
|
||
# 找 tag=a 的元素
|
||
a_elements = [e for para in content for e in para if e.get("tag") == "a"]
|
||
assert len(a_elements) == 1
|
||
assert a_elements[0]["href"] == "https://trace.example.com/tr0"
|
||
assert a_elements[0]["text"] == "trace"
|
||
|
||
|
||
def test_post_no_trace_url_shows_trace_id_prefix():
|
||
hits = _hits_with_meta(1, trace_url=None)
|
||
title, content = format_alert_post(
|
||
hits, window_label="w", phone_map={}, max_detail_per_type=20, max_total=50,
|
||
)
|
||
# 无 trace_url 时:tag=text, text=trace_id[:16]
|
||
detail_texts = [e.get("text", "") for para in content for e in para if e.get("tag") == "text"]
|
||
# trace_id 是 tr0,截 16 位
|
||
assert any("tr0" in t for t in detail_texts)
|
||
|
||
|
||
def test_post_total_truncation_no_detail():
|
||
hits = _hits_with_meta(60)
|
||
title, content = format_alert_post(
|
||
hits, window_label="w", phone_map={}, max_detail_per_type=20, max_total=50,
|
||
)
|
||
# 超 max_total:只有摘要+截断提示,无表头、无明细
|
||
all_text = " ".join(e.get("text", "") for para in content for e in para)
|
||
assert "合计 60 条" in all_text
|
||
assert "时间 | 手机号" not in all_text
|
||
assert "tr0" not in all_text
|
||
assert "分析库" in all_text
|
||
|
||
|
||
def test_post_per_type_truncation():
|
||
hits = _hits_with_meta(25)
|
||
title, content = format_alert_post(
|
||
hits, window_label="w", phone_map={}, max_detail_per_type=20, max_total=50,
|
||
)
|
||
all_text = " ".join(e.get("text", "") for para in content for e in para)
|
||
assert "另有 5 条" in all_text
|
||
|
||
|
||
def test_post_created_at_formatting():
|
||
hits = [
|
||
AlertHit(
|
||
trace_id="tx1",
|
||
alert_type="T1",
|
||
reason="测试",
|
||
app_version="v2.0",
|
||
created_at=datetime(2026, 8, 4, 10, 30),
|
||
trace_url=None,
|
||
user_id=None,
|
||
)
|
||
]
|
||
title, content = format_alert_post(
|
||
hits, window_label="w", phone_map={}, max_detail_per_type=20, max_total=50,
|
||
)
|
||
all_text = " ".join(e.get("text", "") for para in content for e in para)
|
||
assert "08-04 10:30" in all_text
|
||
|
||
|
||
def test_post_no_created_at_shows_dash():
|
||
hits = [
|
||
AlertHit(
|
||
trace_id="tx2",
|
||
alert_type="T1",
|
||
reason="测试",
|
||
app_version=None,
|
||
created_at=None,
|
||
trace_url=None,
|
||
user_id=None,
|
||
)
|
||
]
|
||
title, content = format_alert_post(
|
||
hits, window_label="w", phone_map={}, max_detail_per_type=20, max_total=50,
|
||
)
|
||
all_text = " ".join(e.get("text", "") for para in content for e in para)
|
||
# 无 created_at 时间显示 "-"
|
||
assert "- |" in all_text
|