test_sync_multi_demand_pool.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. """需求池同步内部子阶段的异常隔离测试。"""
  2. from __future__ import annotations
  3. from unittest.mock import MagicMock, patch
  4. from supply_infra.category_match import CategoryMatch
  5. from supply_infra.scheduler.jobs.demand_pool.sync import (
  6. _classify_words,
  7. _sync_pool_rows,
  8. )
  9. @patch(
  10. "supply_infra.scheduler.jobs.demand_pool.sync.GlobalTreeCategoryRepository"
  11. )
  12. @patch(
  13. "supply_infra.scheduler.jobs.demand_pool.sync.DemandBelongCategoryRepository"
  14. )
  15. @patch(
  16. "supply_infra.scheduler.jobs.demand_pool.sync.MultiDemandPoolDiRepository"
  17. )
  18. @patch("supply_infra.scheduler.jobs.demand_pool.sync.get_session")
  19. def test_classification_calls_match_api_once_per_term_and_keeps_partial_success(
  20. mock_get_session,
  21. mock_pool_repo_cls,
  22. mock_belong_repo_cls,
  23. mock_tree_repo_cls,
  24. ) -> None:
  25. mock_get_session.return_value.__enter__.return_value = MagicMock()
  26. mock_pool_repo_cls.return_value.list_demand_names_by_biz_dt.return_value = [
  27. "牺牲 未知词",
  28. "失败词",
  29. ]
  30. mock_belong_repo_cls.return_value.get_existing_names.return_value = set()
  31. mock_belong_repo_cls.return_value.upsert_category_matches.return_value = 1
  32. mock_tree_repo_cls.return_value.get_active_source_id_map.return_value = {
  33. 7392: 88
  34. }
  35. client = MagicMock()
  36. def _match(term: str, *, description: str):
  37. assert description == ""
  38. if term == "失败词":
  39. raise RuntimeError("upstream unavailable")
  40. if term == "未知词":
  41. return None
  42. return CategoryMatch(
  43. term="牺牲",
  44. stable_id=7392,
  45. name="壮烈牺牲",
  46. path="/理念/事件/军事事件/军人事迹/英勇战功/壮烈牺牲",
  47. description="为国为民献出生命",
  48. level=6,
  49. score=1.0,
  50. )
  51. client.match_one.side_effect = _match
  52. result = _classify_words("20260721", client=client)
  53. assert client.match_one.call_count == 3
  54. client.match_one.assert_any_call("牺牲", description="")
  55. client.match_one.assert_any_call("未知词", description="")
  56. client.match_one.assert_any_call("失败词", description="")
  57. assert result["success"] is False
  58. assert result["api_calls"] == 3
  59. assert result["matched"] == 1
  60. assert result["persisted"] == 1
  61. assert result["unmatched_terms"] == ["未知词"]
  62. assert result["failed_items"] == [
  63. {"term": "失败词", "error": "upstream unavailable"}
  64. ]
  65. rows = mock_belong_repo_cls.return_value.upsert_category_matches.call_args.args[0]
  66. assert rows == [
  67. {
  68. "name": "牺牲",
  69. "category_id": 88,
  70. "reason": (
  71. "category_match_api_v2: "
  72. "path=/理念/事件/军事事件/军人事迹/英勇战功/壮烈牺牲; "
  73. "score=1.0000; stable_id=7392"
  74. ),
  75. "is_delete": 0,
  76. }
  77. ]
  78. @patch(
  79. "supply_infra.scheduler.jobs.demand_pool.sync.DemandBelongPoolRelRepository"
  80. )
  81. @patch(
  82. "supply_infra.scheduler.jobs.demand_pool.sync.MultiDemandPoolDiRepository"
  83. )
  84. @patch("supply_infra.scheduler.jobs.demand_pool.sync.get_session")
  85. @patch("supply_infra.scheduler.jobs.demand_pool.sync.get_odps_client")
  86. def test_same_count_content_revision_is_updated(
  87. mock_get_odps,
  88. mock_get_session,
  89. mock_pool_repo_cls,
  90. mock_rel_repo_cls,
  91. ) -> None:
  92. mock_get_odps.return_value.fetch_multi_demand_pool.return_value = [
  93. {
  94. "strategy": "逐月",
  95. "demand_id": "d-1",
  96. "demand_name": "修订后的需求",
  97. "weight": 0.8,
  98. "type": "topic",
  99. "video_list": ["v-2"],
  100. "extend": "new",
  101. }
  102. ]
  103. mock_get_session.return_value.__enter__.return_value = MagicMock()
  104. repo = mock_pool_repo_cls.return_value
  105. repo.list_source_rows_by_biz_dt.return_value = [
  106. {
  107. "id": 11,
  108. "strategy": "逐月",
  109. "demand_id": "d-1",
  110. "demand_name": "修订前的需求",
  111. "weight": 0.5,
  112. "type": "topic",
  113. "video_count": 1,
  114. "video_list": '["v-1"]',
  115. "extend": "old",
  116. "biz_dt": "20260730",
  117. }
  118. ]
  119. repo.delete_by_keys.return_value = 0
  120. repo.bulk_insert.return_value = 0
  121. repo.update_source_fields.return_value = 1
  122. mock_rel_repo_cls.return_value.delete_by_pool_ids.return_value = 0
  123. result = _sync_pool_rows("20260730")
  124. assert result["fetched"] == 1
  125. assert result["inserted"] == 0
  126. assert result["deleted"] == 0
  127. assert result["updated"] == 1
  128. assert result["unchanged"] == 0
  129. assert len(result["source_snapshot_hash"]) == 64
  130. repo.update_source_fields.assert_called_once()
  131. updated_row = repo.update_source_fields.call_args.args[1][0]
  132. assert updated_row["demand_name"] == "修订后的需求"
  133. assert updated_row["video_list"] == '["v-2"]'
  134. @patch(
  135. "supply_infra.scheduler.jobs.demand_pool.sync.DemandBelongPoolRelRepository"
  136. )
  137. @patch(
  138. "supply_infra.scheduler.jobs.demand_pool.sync.MultiDemandPoolDiRepository"
  139. )
  140. @patch("supply_infra.scheduler.jobs.demand_pool.sync.get_session")
  141. @patch("supply_infra.scheduler.jobs.demand_pool.sync.get_odps_client")
  142. def test_unchanged_content_is_not_rewritten(
  143. mock_get_odps,
  144. mock_get_session,
  145. mock_pool_repo_cls,
  146. mock_rel_repo_cls,
  147. ) -> None:
  148. upstream = {
  149. "strategy": "逐月",
  150. "demand_id": "d-1",
  151. "demand_name": "老年 健身",
  152. "weight": 0.8,
  153. "type": "topic",
  154. "video_list": ["v-1"],
  155. "extend": "same",
  156. }
  157. mock_get_odps.return_value.fetch_multi_demand_pool.return_value = [upstream]
  158. mock_get_session.return_value.__enter__.return_value = MagicMock()
  159. repo = mock_pool_repo_cls.return_value
  160. repo.list_source_rows_by_biz_dt.return_value = [
  161. {
  162. "id": 11,
  163. "strategy": "逐月",
  164. "demand_id": "d-1",
  165. "demand_name": "老年 健身",
  166. "weight": 0.8,
  167. "type": "topic",
  168. "video_count": 1,
  169. "video_list": '["v-1"]',
  170. "extend": "same",
  171. "biz_dt": "20260730",
  172. }
  173. ]
  174. repo.delete_by_keys.return_value = 0
  175. repo.bulk_insert.return_value = 0
  176. repo.update_source_fields.return_value = 0
  177. mock_rel_repo_cls.return_value.delete_by_pool_ids.return_value = 0
  178. result = _sync_pool_rows("20260730")
  179. assert result["updated"] == 0
  180. assert result["unchanged"] == 1
  181. repo.update_source_fields.assert_not_called()