test_policy_dispatch.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. from __future__ import annotations
  2. import json
  3. import shutil
  4. from copy import deepcopy
  5. from pathlib import Path
  6. import pytest
  7. from content_agent.errors import ContentAgentError, ErrorCode
  8. from content_agent.integrations.policy_json import JsonPolicyBundleStore, _select_dispatch
  9. RULE_PACK_JSON = Path("product_documents/规则包/douyin_rule_packs.v1.json")
  10. def test_policy_bundle_uses_content_dispatch_and_exports_runtime_contracts():
  11. bundle = JsonPolicyBundleStore().load_policy_bundle("V1")
  12. assert bundle["dispatch_id"] == "dispatch_content"
  13. assert bundle["runtime_stage"] == "V1.0"
  14. assert bundle["target_entity"] == "Content"
  15. assert bundle["content_format"] == "video"
  16. assert bundle["rule_pack_id"] == "douyin_content_discovery_rule_pack_v1"
  17. assert bundle["effect_status_mapping"]
  18. assert bundle["query_effect_aggregation"]
  19. assert bundle["runtime_status_contract"]["query_effect_status"] == [
  20. "success",
  21. "pending",
  22. "failed",
  23. "rule_blocked",
  24. ]
  25. assert bundle["policy_bundle_hash"]
  26. def test_policy_bundle_fails_when_content_dispatch_is_missing(tmp_path):
  27. root = _copy_policy_files(tmp_path)
  28. path = root / "product_documents/规则包/douyin_rule_packs.v1.json"
  29. data = json.loads(path.read_text(encoding="utf-8"))
  30. for dispatch in data["rule_pack_dispatch"]:
  31. if dispatch["dispatch_id"] == "dispatch_content":
  32. dispatch["dispatch_enabled"] = False
  33. path.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
  34. with pytest.raises(ValueError, match="dispatch not found for Content/video"):
  35. JsonPolicyBundleStore(root).load_policy_bundle("V1")
  36. def test_dispatch_conflict_raises_config_error_with_rule_pack_ids(tmp_path):
  37. root = _copy_policy_files(tmp_path)
  38. path = root / "product_documents/规则包/douyin_rule_packs.v1.json"
  39. data = json.loads(path.read_text(encoding="utf-8"))
  40. duplicate = dict(data["rule_pack_dispatch"][0])
  41. duplicate["dispatch_id"] = "dispatch_content_duplicate"
  42. duplicate["priority"] = 2
  43. data["rule_pack_dispatch"].append(duplicate)
  44. path.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
  45. with pytest.raises(ContentAgentError) as exc_info:
  46. JsonPolicyBundleStore(root).load_policy_bundle("V1")
  47. error = exc_info.value
  48. assert error.error_code == ErrorCode.CONFIG_RULE_PACK_DISPATCH_CONFLICT
  49. assert "douyin_content_discovery_rule_pack_v1" in error.message
  50. assert error.detail["conflict_rule_pack_ids"] == [
  51. "douyin_content_discovery_rule_pack_v1",
  52. "douyin_content_discovery_rule_pack_v1",
  53. ]
  54. def test_select_dispatch_still_returns_content_for_default_bundle():
  55. rule_package = json.loads(RULE_PACK_JSON.read_text(encoding="utf-8"))
  56. dispatch = _select_dispatch(rule_package, "V1")
  57. assert dispatch["dispatch_id"] == "dispatch_content"
  58. assert dispatch["target_entity"] == "Content"
  59. def test_select_dispatch_can_select_non_content_when_enabled():
  60. rule_package = json.loads(RULE_PACK_JSON.read_text(encoding="utf-8"))
  61. rule_package = deepcopy(rule_package)
  62. author = next(d for d in rule_package["rule_pack_dispatch"] if d["target_entity"] == "Author")
  63. author["dispatch_enabled"] = True
  64. author["runtime_stage"] = "V1.0"
  65. author["strategy_version"] = "V1"
  66. dispatch = _select_dispatch(
  67. rule_package, "V1", target_entity="Author", content_format=author["content_format"]
  68. )
  69. assert dispatch["rule_pack_id"] == "douyin_author_expand_rule_pack_v1"
  70. def test_load_policy_bundle_keeps_content_shim():
  71. bundle = JsonPolicyBundleStore().load_policy_bundle("V1")
  72. assert bundle["target_entity"] == "Content"
  73. assert bundle["rule_pack_id"] == "douyin_content_discovery_rule_pack_v1"
  74. assert bundle["rule_pack"] is bundle["rule_pack_by_entity"]["Content"]["rule_pack"]
  75. def test_load_policy_bundle_exposes_rule_pack_by_entity():
  76. bundle = JsonPolicyBundleStore().load_policy_bundle("V1")
  77. by_entity = bundle["rule_pack_by_entity"]
  78. assert set(by_entity) == {"Content"}
  79. assert by_entity["Content"]["dispatch"]["dispatch_id"] == "dispatch_content"
  80. assert by_entity["Content"]["rule_pack"]["rule_pack_id"] == bundle["rule_pack_id"]
  81. def test_enabled_author_dispatch_can_be_found_by_entity_without_replacing_content_shim(tmp_path):
  82. root = _copy_policy_files(tmp_path)
  83. path = root / "product_documents/规则包/douyin_rule_packs.v1.json"
  84. data = json.loads(path.read_text(encoding="utf-8"))
  85. author = next(d for d in data["rule_pack_dispatch"] if d["target_entity"] == "Author")
  86. author["dispatch_enabled"] = True
  87. author["runtime_stage"] = "V1.0"
  88. author["strategy_version"] = "V1"
  89. path.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
  90. bundle = JsonPolicyBundleStore(root).load_policy_bundle("V1")
  91. assert bundle["rule_pack_id"] == "douyin_content_discovery_rule_pack_v1"
  92. assert set(bundle["rule_pack_by_entity"]) == {"Content", "Author"}
  93. author_pack = bundle["rule_pack_by_entity"]["Author"]["rule_pack"]
  94. assert author_pack["rule_pack_id"] == "douyin_author_expand_rule_pack_v1"
  95. def test_policy_bundle_fails_when_dispatch_points_to_missing_rule_pack(tmp_path):
  96. root = _copy_policy_files(tmp_path)
  97. path = root / "product_documents/规则包/douyin_rule_packs.v1.json"
  98. data = json.loads(path.read_text(encoding="utf-8"))
  99. for dispatch in data["rule_pack_dispatch"]:
  100. if dispatch["dispatch_id"] == "dispatch_content":
  101. dispatch["rule_pack_id"] = "missing_rule_pack"
  102. path.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
  103. with pytest.raises(ValueError, match="dispatch dispatch_content matched 0 enabled rule packs"):
  104. JsonPolicyBundleStore(root).load_policy_bundle("V1")
  105. def test_policy_bundle_fails_when_dispatch_points_to_disabled_rule_pack(tmp_path):
  106. root = _copy_policy_files(tmp_path)
  107. path = root / "product_documents/规则包/douyin_rule_packs.v1.json"
  108. data = json.loads(path.read_text(encoding="utf-8"))
  109. for rule_pack in data["rule_packs"]:
  110. if rule_pack["rule_pack_id"] == "douyin_content_discovery_rule_pack_v1":
  111. rule_pack["enabled"] = False
  112. path.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
  113. with pytest.raises(ValueError, match="dispatch dispatch_content matched 0 enabled rule packs"):
  114. JsonPolicyBundleStore(root).load_policy_bundle("V1")
  115. def _copy_policy_files(tmp_path: Path) -> Path:
  116. root = tmp_path / "repo"
  117. (root / "product_documents/规则包").mkdir(parents=True)
  118. shutil.copy(
  119. "product_documents/规则包/douyin_rule_packs.v1.json",
  120. root / "product_documents/规则包/douyin_rule_packs.v1.json",
  121. )
  122. (root / "product_documents/抖音游走策略").mkdir(parents=True)
  123. shutil.copy(
  124. "product_documents/抖音游走策略/douyin_walk_strategy.v1.json",
  125. root / "product_documents/抖音游走策略/douyin_walk_strategy.v1.json",
  126. )
  127. return root