test_sql_state_manager.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. SQLStateManager tests
  5. """
  6. import json
  7. import sys
  8. import pytest
  9. import data_modules.sql_state_manager as sql_state_manager_module
  10. from data_modules.sql_state_manager import SQLStateManager, EntityData
  11. from data_modules.index_manager import EntityMeta
  12. @pytest.fixture
  13. def temp_project(tmp_path):
  14. from data_modules.config import DataModulesConfig
  15. cfg = DataModulesConfig.from_project_root(tmp_path)
  16. cfg.ensure_dirs()
  17. return cfg
  18. def test_sql_state_manager_entity_and_alias(temp_project):
  19. manager = SQLStateManager(temp_project)
  20. entity = EntityData(
  21. id="xiaoyan",
  22. type="角色",
  23. name="萧炎",
  24. tier="核心",
  25. current={"realm": "斗师"},
  26. aliases=["炎帝", "小炎子"],
  27. is_protagonist=True,
  28. )
  29. assert manager.upsert_entity(entity) is True
  30. assert manager.upsert_entity(entity) is False
  31. fetched = manager.get_entity("xiaoyan")
  32. assert "炎帝" in fetched["aliases"]
  33. by_type = manager.get_entities_by_type("角色")
  34. assert any(e["id"] == "xiaoyan" for e in by_type)
  35. core = manager.get_core_entities()
  36. assert any(e["id"] == "xiaoyan" for e in core)
  37. protagonist = manager.get_protagonist()
  38. assert protagonist["id"] == "xiaoyan"
  39. resolved = manager.resolve_alias("炎帝")
  40. assert any(r["id"] == "xiaoyan" for r in resolved)
  41. assert manager.update_entity_current("xiaoyan", {"realm": "斗王"}) is True
  42. updated = manager.get_entity("xiaoyan")
  43. assert updated["current_json"]["realm"] == "斗王"
  44. def test_sql_state_manager_state_changes_and_relationships(temp_project):
  45. manager = SQLStateManager(temp_project)
  46. manager.upsert_entity(
  47. EntityData(id="xiaoyan", type="角色", name="萧炎", current={})
  48. )
  49. change_id = manager.record_state_change(
  50. entity_id="xiaoyan",
  51. field="realm",
  52. old_value="斗者",
  53. new_value="斗师",
  54. reason="突破",
  55. chapter=2,
  56. )
  57. assert change_id > 0
  58. assert len(manager.get_entity_state_changes("xiaoyan")) == 1
  59. assert len(manager.get_recent_state_changes(limit=5)) == 1
  60. assert len(manager.get_chapter_state_changes(2)) == 1
  61. assert manager.upsert_relationship(
  62. from_entity="xiaoyan",
  63. to_entity="yaolao",
  64. type="师徒",
  65. description="收徒",
  66. chapter=1,
  67. )
  68. rels = manager.get_entity_relationships("xiaoyan", direction="from")
  69. assert len(rels) == 1
  70. between = manager.get_relationship_between("xiaoyan", "yaolao")
  71. assert len(between) == 1
  72. assert len(manager.get_recent_relationships(limit=5)) >= 1
  73. def test_sql_state_manager_process_chapter_entities_and_exports(temp_project):
  74. manager = SQLStateManager(temp_project)
  75. stats = manager.process_chapter_entities(
  76. chapter=10,
  77. entities_appeared=[{"id": "xiaoyan", "mentions": ["萧炎"], "confidence": 0.9}],
  78. entities_new=[
  79. {"suggested_id": "yaolao", "name": "药老", "type": "角色", "tier": "重要"}
  80. ],
  81. state_changes=[
  82. {"entity_id": "yaolao", "field": "status", "old": "", "new": "出场", "reason": "登场"}
  83. ],
  84. relationships_new=[
  85. {"from": "xiaoyan", "to": "yaolao", "type": "师徒", "description": "收徒"}
  86. ],
  87. )
  88. assert stats["entities_created"] >= 1
  89. assert stats["relationships"] == 1
  90. entities_v3 = manager.export_to_entities_v3_format()
  91. assert "角色" in entities_v3
  92. alias_index = manager.export_to_alias_index_format()
  93. assert isinstance(alias_index, dict)
  94. def test_sql_state_manager_existing_entity_updates_and_stats(temp_project):
  95. manager = SQLStateManager(temp_project)
  96. manager.upsert_entity(
  97. EntityData(id="xiaoyan", type="角色", name="萧炎", current={"hp": 5})
  98. )
  99. stats = manager.process_chapter_entities(
  100. chapter=3,
  101. entities_appeared=[{"id": "xiaoyan", "mentions": ["萧炎"], "confidence": 0.9}],
  102. entities_new=[],
  103. state_changes=[
  104. {"entity_id": "xiaoyan", "field": "hp", "old": 5, "new": 0, "reason": "受伤"}
  105. ],
  106. relationships_new=[
  107. {"from_entity": "xiaoyan", "to_entity": "yaolao", "type": "师徒", "description": "收徒"}
  108. ],
  109. )
  110. assert stats["entities_updated"] >= 1
  111. assert stats["state_changes"] == 1
  112. updated = manager.get_entity("xiaoyan")
  113. assert updated["current_json"]["hp"] == 0
  114. rels = manager.get_entity_relationships("yaolao", direction="to")
  115. assert rels
  116. stats_summary = manager.get_stats()
  117. assert "entities" in stats_summary
  118. exported = manager.export_to_entities_v3_format()
  119. assert exported["角色"]["xiaoyan"]["canonical_name"] == "萧炎"
  120. def test_sql_state_manager_process_chapter_skips_and_existing(temp_project):
  121. manager = SQLStateManager(temp_project)
  122. manager.upsert_entity(EntityData(id="xiaoyan", type="角色", name="萧炎"))
  123. stats = manager.process_chapter_entities(
  124. chapter=1,
  125. entities_appeared=[{"mentions": ["无ID"]}, {"id": "xiaoyan", "mentions": ["萧炎"]}],
  126. entities_new=[{"name": "无ID"}, {"suggested_id": "xiaoyan", "name": "萧炎"}],
  127. state_changes=[{"field": "realm"}, {"entity_id": "xiaoyan", "field": "hp", "old": 1, "new": 1}],
  128. relationships_new=[{"from": "xiaoyan", "to": ""}],
  129. )
  130. assert stats["entities_updated"] >= 1
  131. assert stats["relationships"] == 0
  132. def test_sql_state_manager_export_protagonist_and_cli(temp_project, monkeypatch, capsys):
  133. manager = SQLStateManager(temp_project)
  134. def run_cli(args):
  135. monkeypatch.setattr(sys, "argv", args)
  136. sql_state_manager_module.main()
  137. return capsys.readouterr().out
  138. out = run_cli(["sql_state_manager", "--project-root", str(temp_project.project_root), "get-protagonist"])
  139. assert "未设置主角" in out
  140. manager.upsert_entity(
  141. EntityData(id="xiaoyan", type="角色", name="萧炎", is_protagonist=True)
  142. )
  143. exported = manager.export_to_entities_v3_format()
  144. assert exported["角色"]["xiaoyan"]["is_protagonist"] is True
  145. out = run_cli(["sql_state_manager", "--project-root", str(temp_project.project_root), "get-protagonist"])
  146. assert "萧炎" in out
  147. out = run_cli(["sql_state_manager", "--project-root", str(temp_project.project_root), "stats"])
  148. assert "entities" in out
  149. out = run_cli(["sql_state_manager", "--project-root", str(temp_project.project_root), "get-core-entities"])
  150. assert "萧炎" in out
  151. out = run_cli(["sql_state_manager", "--project-root", str(temp_project.project_root), "export-entities-v3"])
  152. assert "角色" in out
  153. out = run_cli(["sql_state_manager", "--project-root", str(temp_project.project_root), "export-alias-index"])
  154. assert isinstance(json.loads(out or "{}"), dict)
  155. payload = json.dumps({"entities_appeared": [], "entities_new": [], "state_changes": [], "relationships_new": []})
  156. out = run_cli([
  157. "sql_state_manager",
  158. "--project-root",
  159. str(temp_project.project_root),
  160. "process-chapter",
  161. "--chapter",
  162. "2",
  163. "--data",
  164. payload,
  165. ])
  166. assert "已处理第 2 章" in out