test_vector_projection_writer.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """VectorProjectionWriter 单元测试。"""
  4. import pytest
  5. from data_modules.vector_projection_writer import VectorProjectionWriter
  6. def test_event_to_text_formats_power_breakthrough():
  7. writer = VectorProjectionWriter.__new__(VectorProjectionWriter)
  8. event = {
  9. "event_type": "power_breakthrough",
  10. "chapter": 47,
  11. "subject": "韩立",
  12. "payload": {"field": "realm", "new": "筑基初期"},
  13. }
  14. text = writer._event_to_text(event)
  15. assert "第47章" in text
  16. assert "韩立" in text
  17. assert "筑基初期" in text
  18. def test_delta_to_text_formats_relationship():
  19. writer = VectorProjectionWriter.__new__(VectorProjectionWriter)
  20. delta = {
  21. "from_entity": "韩立",
  22. "to_entity": "陈巧倩",
  23. "relationship_type": "合作",
  24. "chapter": 47,
  25. }
  26. text = writer._delta_to_text(delta)
  27. assert "第47章" in text
  28. assert "韩立" in text
  29. assert "陈巧倩" in text
  30. assert "合作" in text
  31. def test_collect_chunks_from_commit():
  32. writer = VectorProjectionWriter.__new__(VectorProjectionWriter)
  33. payload = {
  34. "meta": {"chapter": 47, "status": "accepted"},
  35. "accepted_events": [
  36. {
  37. "event_type": "power_breakthrough",
  38. "chapter": 47,
  39. "subject": "韩立",
  40. "payload": {"field": "realm", "new": "筑基初期"},
  41. },
  42. ],
  43. "entity_deltas": [
  44. {
  45. "from_entity": "韩立",
  46. "to_entity": "陈巧倩",
  47. "relationship_type": "合作",
  48. "chapter": 47,
  49. },
  50. ],
  51. }
  52. chunks = writer._collect_chunks(payload)
  53. assert len(chunks) == 2
  54. assert chunks[0]["chunk_type"] == "event"
  55. assert chunks[1]["chunk_type"] == "entity_delta"
  56. assert chunks[0]["chunk_id"] != chunks[1]["chunk_id"]
  57. def test_collect_chunks_assigns_unique_ids_for_same_chapter_events():
  58. writer = VectorProjectionWriter.__new__(VectorProjectionWriter)
  59. payload = {
  60. "meta": {"chapter": 47, "status": "accepted"},
  61. "accepted_events": [
  62. {
  63. "event_type": "character_state_changed",
  64. "chapter": 47,
  65. "subject": "韩立",
  66. "payload": {"field": "状态", "new": "警觉"},
  67. },
  68. {
  69. "event_type": "character_state_changed",
  70. "chapter": 47,
  71. "subject": "陈巧倩",
  72. "payload": {"field": "状态", "new": "迟疑"},
  73. },
  74. ],
  75. "entity_deltas": [],
  76. }
  77. chunks = writer._collect_chunks(payload)
  78. assert len(chunks) == 2
  79. assert len({chunk["chunk_id"] for chunk in chunks}) == 2
  80. assert all(chunk["scene_index"] == 0 for chunk in chunks)
  81. def test_collect_chunks_keeps_event_id_stable_when_order_changes():
  82. writer = VectorProjectionWriter.__new__(VectorProjectionWriter)
  83. event_a = {
  84. "event_id": "evt-a",
  85. "event_type": "character_state_changed",
  86. "chapter": 47,
  87. "subject": "韩立",
  88. "payload": {"field": "状态", "new": "警觉"},
  89. }
  90. event_b = {
  91. "event_id": "evt-b",
  92. "event_type": "character_state_changed",
  93. "chapter": 47,
  94. "subject": "陈巧倩",
  95. "payload": {"field": "状态", "new": "迟疑"},
  96. }
  97. first = writer._collect_chunks(
  98. {"meta": {"chapter": 47}, "accepted_events": [event_a, event_b], "entity_deltas": []}
  99. )
  100. second = writer._collect_chunks(
  101. {"meta": {"chapter": 47}, "accepted_events": [event_b, event_a], "entity_deltas": []}
  102. )
  103. first_ids = {chunk["content"]: chunk["chunk_id"] for chunk in first}
  104. second_ids = {chunk["content"]: chunk["chunk_id"] for chunk in second}
  105. assert first_ids == second_ids
  106. def test_rejected_commit_returns_not_applied():
  107. writer = VectorProjectionWriter.__new__(VectorProjectionWriter)
  108. writer.project_root = None
  109. result = writer.apply({"meta": {"status": "rejected", "chapter": 1}})
  110. assert result["applied"] is False
  111. def test_store_zero_for_required_chunks_is_error(monkeypatch, tmp_path):
  112. writer = VectorProjectionWriter(tmp_path)
  113. monkeypatch.setattr(writer, "_store_chunks", lambda chunks: 0)
  114. result = writer.apply(
  115. {
  116. "meta": {"status": "accepted", "chapter": 47},
  117. "summary_text": "韩立在坊市发现丹方线索。",
  118. "accepted_events": [],
  119. "entity_deltas": [],
  120. }
  121. )
  122. assert result["applied"] is False
  123. assert result["reason"] == "error:store_failed"
  124. def test_collect_chunks_includes_summary_and_scenes():
  125. writer = VectorProjectionWriter.__new__(VectorProjectionWriter)
  126. payload = {
  127. "meta": {"chapter": 47, "status": "accepted"},
  128. "summary_text": "韩立在坊市发现丹方线索。",
  129. "scenes": [
  130. {"index": 1, "summary": "韩立入坊市观察摊位", "location": "坊市"},
  131. {"scene_index": 2, "content": "陈巧倩暗中提醒韩立有人跟踪。"},
  132. ],
  133. "accepted_events": [],
  134. "entity_deltas": [],
  135. }
  136. chunks = writer._collect_chunks(payload)
  137. by_type = {}
  138. for chunk in chunks:
  139. by_type.setdefault(chunk["chunk_type"], []).append(chunk)
  140. assert by_type["summary"][0]["chunk_id"] == "ch0047_summary"
  141. assert by_type["summary"][0]["parent_chunk_id"] is None
  142. assert by_type["scene"][0]["parent_chunk_id"] == "ch0047_summary"
  143. assert by_type["scene"][0]["content"].startswith("坊市:")
  144. assert any(chunk["scene_index"] == 2 for chunk in by_type["scene"])
  145. @pytest.mark.asyncio
  146. async def test_run_store_coro_works_inside_active_event_loop():
  147. writer = VectorProjectionWriter.__new__(VectorProjectionWriter)
  148. async def store():
  149. return 3
  150. assert writer._run_store_coro(store()) == 3