test_archive_manager.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. import json
  4. from pathlib import Path
  5. import pytest
  6. def _load_archive_module():
  7. import sys
  8. scripts_dir = Path(__file__).resolve().parents[2]
  9. if str(scripts_dir) not in sys.path:
  10. sys.path.insert(0, str(scripts_dir))
  11. import archive_manager
  12. return archive_manager
  13. @pytest.fixture
  14. def archive_env(tmp_path):
  15. webnovel = tmp_path / ".webnovel"
  16. webnovel.mkdir(parents=True, exist_ok=True)
  17. state_path = webnovel / "state.json"
  18. state_path.write_text(
  19. '{"progress":{"current_chapter":10},"plot_threads":{},"review_checkpoints":[]}',
  20. encoding="utf-8",
  21. )
  22. return tmp_path
  23. def test_archive_remove_from_state_missing_sections(archive_env):
  24. module = _load_archive_module()
  25. manager = module.ArchiveManager(project_root=archive_env)
  26. state = {
  27. "progress": {"current_chapter": 50},
  28. }
  29. updated = manager.remove_from_state(state, inactive_chars=[], resolved_threads=[], old_reviews=[])
  30. assert updated.get("progress", {}).get("current_chapter") == 50
  31. def test_archive_check_trigger_conditions_edges(archive_env):
  32. module = _load_archive_module()
  33. manager = module.ArchiveManager(project_root=archive_env)
  34. manager.config["chapter_trigger"] = 10
  35. manager.config["file_size_trigger_mb"] = 9999.0
  36. trigger = manager.check_trigger_conditions({"progress": {"current_chapter": 20}})
  37. assert trigger["chapter_trigger"] is True
  38. assert trigger["should_archive"] is True
  39. def test_archive_identify_old_reviews_handles_mixed_formats(archive_env):
  40. module = _load_archive_module()
  41. manager = module.ArchiveManager(project_root=archive_env)
  42. manager.config["review_old_threshold"] = 5
  43. state = {
  44. "progress": {"current_chapter": 30},
  45. "review_checkpoints": [
  46. {"chapters": "20-22", "report": "r1.md"},
  47. {"chapter_range": [10, 12], "date": "2026-01-01"},
  48. {"report": "Review_Ch5-6.md"},
  49. ],
  50. }
  51. results = manager.identify_old_reviews(state)
  52. assert len(results) == 3
  53. assert all(row["chapters_since_review"] >= 5 for row in results)
  54. def test_save_archive_uses_atomic_write_json(archive_env, monkeypatch):
  55. module = _load_archive_module()
  56. manager = module.ArchiveManager(project_root=archive_env)
  57. calls = []
  58. def fake_atomic_write_json(path, data, *, use_lock=True, backup=True, indent=2):
  59. calls.append((path, data, use_lock, backup, indent))
  60. Path(path).write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
  61. monkeypatch.setattr(module, "atomic_write_json", fake_atomic_write_json)
  62. manager.save_archive(manager.characters_archive, [{"name": "李雪"}])
  63. assert calls == [(manager.characters_archive, [{"name": "李雪"}], True, True, 2)]
  64. def test_restore_character_keeps_archive_when_sqlite_restore_fails(archive_env, monkeypatch):
  65. module = _load_archive_module()
  66. manager = module.ArchiveManager(project_root=archive_env)
  67. archived = [
  68. {
  69. "id": "li_xue",
  70. "name": "李雪",
  71. "tier": "支线",
  72. "archived_at": "2026-06-10T00:00:00",
  73. }
  74. ]
  75. manager.characters_archive.write_text(json.dumps(archived, ensure_ascii=False), encoding="utf-8")
  76. before = manager.characters_archive.read_text(encoding="utf-8")
  77. def fail_restore(*args, **kwargs):
  78. raise RuntimeError("sqlite down")
  79. monkeypatch.setattr(manager._index_manager, "update_entity_field", fail_restore)
  80. assert manager.restore_character("李雪") is False
  81. assert manager.characters_archive.read_text(encoding="utf-8") == before
  82. def test_restore_character_deletes_archive_after_sqlite_restore_succeeds(archive_env, monkeypatch):
  83. module = _load_archive_module()
  84. manager = module.ArchiveManager(project_root=archive_env)
  85. archived = [
  86. {
  87. "id": "li_xue",
  88. "name": "李雪",
  89. "tier": "支线",
  90. "archived_at": "2026-06-10T00:00:00",
  91. }
  92. ]
  93. manager.characters_archive.write_text(json.dumps(archived, ensure_ascii=False), encoding="utf-8")
  94. calls = []
  95. def restore_status(entity_id, field, value):
  96. calls.append((entity_id, field, value))
  97. monkeypatch.setattr(manager._index_manager, "update_entity_field", restore_status)
  98. assert manager.restore_character("李雪") is True
  99. assert calls == [("li_xue", "status", "active")]
  100. assert json.loads(manager.characters_archive.read_text(encoding="utf-8")) == []