test_portrait_cache.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. """M9 修复:作者画像按作者缓存(画像是作者级,同 run 内同作者只拉一次)。"""
  2. from content_agent.business_modules.progressive_screening import _ProgressiveContext
  3. class CountingPortraitClient:
  4. def __init__(self):
  5. self.calls = []
  6. def fetch_account_fans_portrait(self, account_id):
  7. self.calls.append(account_id)
  8. return {
  9. "fans": {
  10. "age": {
  11. "data": {
  12. "41-50": {"percentage": "31.73%", "preference": "210"},
  13. "50-": {"percentage": "29.76%", "preference": "130"},
  14. }
  15. }
  16. }
  17. }
  18. def _ctx(client):
  19. return _ProgressiveContext(
  20. run_id="r", policy_run_id="p", source_context={}, policy_bundle={},
  21. platform_client=client, runtime=None, gemini_video_client=None,
  22. limiter=None, archive_dispatcher=None, platform="douyin", portrait_client=client,
  23. )
  24. def _bundle(author, q=80, p=70):
  25. return {
  26. "pattern_match_result": {"query_relevance_score": q},
  27. "content_engagement_metrics": {"platform_performance": {"platform_performance_score": p}},
  28. "content": {"author": {"platform_author_id": author}},
  29. }
  30. def test_portrait_cached_by_author():
  31. client = CountingPortraitClient()
  32. ctx = _ctx(client)
  33. bundles = [_bundle("a1"), _bundle("a1"), _bundle("a2")]
  34. ctx._inject_fifty_plus(bundles)
  35. assert client.calls == ["a1", "a2"] # 同作者只拉一次
  36. assert all(b["content_audience_50plus"]["status"] == "ok" for b in bundles)
  37. def test_portrait_fetched_when_query_passes_regardless_of_platform():
  38. # 修复:门只看 query 相关性,平台分再低也拉画像、算适老性(原来 platform<65 会跳过)。
  39. client = CountingPortraitClient()
  40. ctx = _ctx(client)
  41. bundles = [_bundle("a1", q=70, p=20)] # 平台仅 20
  42. ctx._inject_fifty_plus(bundles)
  43. assert client.calls == ["a1"]
  44. assert bundles[0]["content_audience_50plus"]["status"] == "ok"
  45. def test_portrait_skipped_when_query_below_gate():
  46. client = CountingPortraitClient()
  47. ctx = _ctx(client)
  48. bundles = [_bundle("a1", q=60, p=90)] # 相关性 60 < 65 → 不拉
  49. ctx._inject_fifty_plus(bundles)
  50. assert client.calls == []
  51. assert bundles[0]["content_audience_50plus"]["status"] == "not_attempted"
  52. class FlakyPortraitClient:
  53. def __init__(self, fail_times):
  54. self.fail_times = fail_times
  55. self.calls = 0
  56. def fetch_account_fans_portrait(self, account_id):
  57. self.calls += 1
  58. if self.calls <= self.fail_times:
  59. raise RuntimeError("热点宝 429")
  60. return {"fans": {"age": {"data": {"50-": {"percentage": "40%", "preference": "150"}}}}}
  61. def test_portrait_retry_recovers_transient_failure():
  62. client = FlakyPortraitClient(fail_times=1) # 第一次失败、第二次成功
  63. ctx = _ctx(client)
  64. bundles = [_bundle("a1")]
  65. ctx._inject_fifty_plus(bundles)
  66. assert client.calls == 2 # 重试了一次
  67. assert bundles[0]["content_audience_50plus"]["status"] == "ok"
  68. def test_portrait_unavailable_records_failure_reason():
  69. client = FlakyPortraitClient(fail_times=9) # 一直失败
  70. ctx = _ctx(client)
  71. bundles = [_bundle("a1")]
  72. ctx._inject_fifty_plus(bundles)
  73. block = bundles[0]["content_audience_50plus"]
  74. assert block["status"] == "unavailable"
  75. assert "热点宝 429" in block["failure_reason"] # 原因被记下,不再吞掉