test_sync_global_tree.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. from __future__ import annotations
  2. from unittest.mock import MagicMock, patch
  3. import pytest
  4. from supply_infra.odps.client import ODPSClient
  5. from supply_infra.scheduler.jobs.sync_global_tree_odps_to_mysql import (
  6. _allocate_local_ids,
  7. _to_category_source_rows,
  8. sync_global_tree_odps_to_mysql,
  9. )
  10. def test_global_category_sql_uses_stable_tree_and_no_element_table() -> None:
  11. client = ODPSClient("id", "key", "project", "endpoint")
  12. client.execute_sql = MagicMock(return_value=[])
  13. client.fetch_global_categories("20260731")
  14. sql = client.execute_sql.call_args.args[0]
  15. assert "loghubods.global_category" in sql
  16. assert "stable_id,name,description,level,parent_stable_id" in sql
  17. assert "dt = '20260731'" in sql
  18. assert "retired_at_execution_id IS NULL" in sql
  19. assert "source_type = '实质'" in sql
  20. assert "ORDER BY level" in sql
  21. assert "global_tree_element" not in sql
  22. assert "pattern_mining_element" not in sql
  23. def test_stable_ids_are_mapped_to_existing_and_new_local_ids() -> None:
  24. categories = _to_category_source_rows(
  25. [
  26. {
  27. "stable_id": 1,
  28. "name": "理念",
  29. "description": "",
  30. "level": 1,
  31. "parent_stable_id": None,
  32. },
  33. {
  34. "stable_id": 7392,
  35. "name": "壮烈牺牲",
  36. "description": "描述",
  37. "level": 6,
  38. "parent_stable_id": 1,
  39. },
  40. ]
  41. )
  42. mapping, failed = _allocate_local_ids(categories, {1: 10}, 11)
  43. assert mapping == {1: 10, 7392: 11}
  44. assert failed == set()
  45. @patch(
  46. "supply_infra.scheduler.jobs.sync_global_tree_odps_to_mysql."
  47. "GlobalTreeCategoryRepository"
  48. )
  49. @patch("supply_infra.scheduler.jobs.sync_global_tree_odps_to_mysql.get_session")
  50. @patch("supply_infra.scheduler.jobs.sync_global_tree_odps_to_mysql.get_odps_client")
  51. def test_tree_sync_does_not_read_or_write_global_tree_element(
  52. mock_get_odps,
  53. mock_get_session,
  54. mock_repo_cls,
  55. ) -> None:
  56. mock_get_odps.return_value.fetch_global_categories.return_value = [
  57. {
  58. "stable_id": 1,
  59. "name": "理念",
  60. "description": "root",
  61. "level": 1,
  62. "parent_stable_id": None,
  63. },
  64. {
  65. "stable_id": 7392,
  66. "name": "壮烈牺牲",
  67. "description": "leaf",
  68. "level": 2,
  69. "parent_stable_id": 1,
  70. },
  71. ]
  72. mock_get_session.return_value.__enter__.return_value = MagicMock()
  73. repository = mock_repo_cls.return_value
  74. repository.count_active.return_value = 1
  75. repository.get_source_id_map.return_value = {1: 10}
  76. repository.get_max_id.return_value = 10
  77. repository.bulk_upsert.return_value = 2
  78. repository.mark_missing_deleted.return_value = 0
  79. result = sync_global_tree_odps_to_mysql("20260731")
  80. assert result["categories_inserted"] == 1
  81. assert result["global_tree_element_used"] is False
  82. rows = repository.bulk_upsert.call_args.args[0]
  83. assert rows[1]["source_id"] == 7392
  84. assert rows[1]["parent_id"] == 10
  85. @patch("supply_infra.scheduler.jobs.sync_global_tree_odps_to_mysql.get_odps_client")
  86. def test_empty_global_category_partition_fails_closed(mock_get_odps) -> None:
  87. mock_get_odps.return_value.fetch_global_categories.return_value = []
  88. with pytest.raises(RuntimeError, match="returned no active categories"):
  89. sync_global_tree_odps_to_mysql("20260731")
  90. @patch(
  91. "supply_infra.scheduler.jobs.sync_global_tree_odps_to_mysql."
  92. "GlobalTreeCategoryRepository"
  93. )
  94. @patch("supply_infra.scheduler.jobs.sync_global_tree_odps_to_mysql.get_session")
  95. @patch("supply_infra.scheduler.jobs.sync_global_tree_odps_to_mysql.get_odps_client")
  96. def test_partial_tree_partition_does_not_retire_existing_tree(
  97. mock_get_odps,
  98. mock_get_session,
  99. mock_repo_cls,
  100. ) -> None:
  101. mock_get_odps.return_value.fetch_global_categories.return_value = [
  102. {
  103. "stable_id": 1,
  104. "name": "理念",
  105. "description": "",
  106. "level": 1,
  107. "parent_stable_id": None,
  108. }
  109. ]
  110. mock_get_session.return_value.__enter__.return_value = MagicMock()
  111. repository = mock_repo_cls.return_value
  112. repository.count_active.return_value = 100
  113. with pytest.raises(RuntimeError, match="dropped below safety threshold"):
  114. sync_global_tree_odps_to_mysql("20260731")
  115. repository.bulk_upsert.assert_not_called()
  116. repository.mark_missing_deleted.assert_not_called()