test_query_effect_aggregation.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. from __future__ import annotations
  2. from content_agent.business_modules.run_record import recorder
  3. from content_agent.integrations.policy_json import JsonPolicyBundleStore
  4. from content_agent.run_service import RunService
  5. from content_agent.schemas import RunStartRequest
  6. from tests.p1_helpers import FakeQueryVariantClient, REAL_SOURCE_FIXTURE
  7. def test_search_clues_aggregate_query_effect_status_from_decisions(tmp_path):
  8. service = RunService(
  9. runtime_root=tmp_path / "runtime" / "v1",
  10. query_variant_client=FakeQueryVariantClient(),
  11. )
  12. state = service.start_run(
  13. RunStartRequest(platform_mode="mock", source=str(REAL_SOURCE_FIXTURE))
  14. )
  15. clues = {
  16. clue["search_query_id"]: clue
  17. for clue in service.read_jsonl(state["run_id"], "search_clues.jsonl")
  18. }
  19. assert clues["q_001"]["search_query_effect_status"] == "failed"
  20. assert clues["q_001"]["effect_status_counts"] == {"failed": 2}
  21. assert clues["q_001"]["query_aggregation_id"] == "agg_query_failed"
  22. assert clues["q_001"]["raw_payload"]["query_aggregation_id"] == "agg_query_failed"
  23. assert clues["q_001"]["walk_next_step"] == "stop_search_query"
  24. assert clues["q_002"]["search_query_effect_status"] == "failed"
  25. assert clues["q_002"]["effect_status_counts"] == {"failed": 1}
  26. assert clues["q_002"]["query_aggregation_id"] == "agg_query_failed"
  27. assert clues["q_002"]["walk_next_step"] == "stop_search_query"
  28. def test_rule_blocked_only_query_aggregates_to_rule_blocked():
  29. runtime = _RecordingRuntime()
  30. policy_bundle = JsonPolicyBundleStore().load_policy_bundle("V4")
  31. recorder.run(
  32. run_id="run_001",
  33. policy_run_id="policy_run_001",
  34. search_queries=[
  35. {
  36. "search_query_id": "q_001",
  37. "search_query": "爱国情感",
  38. "discovery_start_source": "pattern_seed",
  39. "previous_discovery_step": "search_query_generated",
  40. }
  41. ],
  42. discovered_content_items=[
  43. {
  44. "platform_content_id": "content_rule_blocked",
  45. "search_query_id": "q_001",
  46. }
  47. ],
  48. decisions=[
  49. {
  50. "decision_target_id": "content_rule_blocked",
  51. "decision_action": "REJECT_CONTENT",
  52. "search_query_effect_status": "rule_blocked",
  53. }
  54. ],
  55. source_path_record_basis=[],
  56. policy_bundle=policy_bundle,
  57. runtime=runtime,
  58. )
  59. clue = runtime.rows["search_clues.jsonl"][0]
  60. assert clue["search_query_effect_status"] == "rule_blocked"
  61. assert clue["query_aggregation_id"] == "agg_query_rule_blocked"
  62. assert clue["raw_payload"]["query_aggregation_id"] == "agg_query_rule_blocked"
  63. assert clue["walk_next_step"] == "stop_search_query"
  64. def test_platform_query_failure_stays_failed(tmp_path):
  65. service = RunService(
  66. runtime_root=tmp_path / "runtime" / "v1",
  67. query_variant_client=FakeQueryVariantClient(),
  68. )
  69. service._platform_client = lambda platform, platform_mode: _PartialFailurePlatformClient()
  70. state = service.start_run(
  71. RunStartRequest(platform_mode="real", source=str(REAL_SOURCE_FIXTURE))
  72. )
  73. failed_clue = next(
  74. clue for clue in service.read_jsonl(state["run_id"], "search_clues.jsonl")
  75. if clue["search_query_id"] == "q_002"
  76. )
  77. assert failed_clue["search_query_effect_status"] == "failed"
  78. assert failed_clue["query_aggregation_id"] == "platform_query_failure"
  79. assert failed_clue["walk_next_step"] == "stop_search_query"
  80. class _PartialFailurePlatformClient:
  81. def search(self, query):
  82. if query["search_query_id"] == "q_002":
  83. raise RuntimeError("platform unavailable")
  84. return []
  85. class _RecordingRuntime:
  86. def __init__(self):
  87. self.rows = {}
  88. def append_jsonl(self, _run_id, filename, rows):
  89. self.rows[filename] = rows