2
0

test_failure_list.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. """
  2. Unit tests for the failure list manager module.
  3. Tests cover failure list recording, export, and retry functionality.
  4. """
  5. import sys
  6. from unittest.mock import Mock
  7. from pathlib import Path
  8. import tempfile
  9. import json
  10. # Mock torch and transformers before importing
  11. sys_mock = Mock()
  12. sys.modules["torch"] = sys_mock
  13. sys.modules["transformers"] = sys_mock
  14. import pytest
  15. from src.repository.failure_list import (
  16. FailureListManager,
  17. FailedTranslation,
  18. )
  19. class TestFailedTranslation:
  20. """Test cases for FailedTranslation dataclass."""
  21. def test_create_failed_translation(self):
  22. """Test creating a failed translation record."""
  23. failure = FailedTranslation(
  24. work_id="test_work",
  25. chapter_index=5,
  26. error_type="ValueError",
  27. error_message="Test error",
  28. )
  29. assert failure.work_id == "test_work"
  30. assert failure.chapter_index == 5
  31. assert failure.error_type == "ValueError"
  32. assert failure.error_message == "Test error"
  33. assert failure.resolved is False
  34. assert failure.retry_count == 0
  35. def test_to_dict(self):
  36. """Test converting failed translation to dictionary."""
  37. failure = FailedTranslation(
  38. work_id="test_work",
  39. chapter_index=5,
  40. error_type="ValueError",
  41. error_message="Test error",
  42. source_text="Test content",
  43. )
  44. data = failure.to_dict()
  45. assert data["work_id"] == "test_work"
  46. assert data["chapter_index"] == 5
  47. assert data["error_type"] == "ValueError"
  48. assert data["source_text"] == "Test content"
  49. assert "timestamp" in data
  50. def test_from_dict(self):
  51. """Test creating failed translation from dictionary."""
  52. data = {
  53. "work_id": "test_work",
  54. "chapter_index": 5,
  55. "error_type": "ValueError",
  56. "error_message": "Test error",
  57. "timestamp": "2024-01-01T00:00:00",
  58. "retry_count": 2,
  59. "source_text": "Test content",
  60. "resolved": False,
  61. }
  62. failure = FailedTranslation.from_dict(data)
  63. assert failure.work_id == "test_work"
  64. assert failure.chapter_index == 5
  65. assert failure.retry_count == 2
  66. class TestFailureListManager:
  67. """Test cases for FailureListManager class."""
  68. @pytest.fixture
  69. def temp_dir(self):
  70. """Create a temporary directory for testing."""
  71. with tempfile.TemporaryDirectory() as tmp:
  72. yield Path(tmp)
  73. @pytest.fixture
  74. def manager(self, temp_dir):
  75. """Create a failure list manager for testing."""
  76. return FailureListManager(temp_dir)
  77. def test_init(self, temp_dir):
  78. """Test FailureListManager initialization."""
  79. manager = FailureListManager(temp_dir)
  80. assert manager.storage_dir == temp_dir
  81. assert manager.failure_list_path == temp_dir / "translate_failed.jsonl"
  82. def test_record_failure(self, manager):
  83. """Test recording a failure."""
  84. error = ValueError("Test error")
  85. failure = manager.record_failure(
  86. work_id="test_work",
  87. chapter_index=5,
  88. error=error,
  89. source_text="Test content"
  90. )
  91. assert failure.work_id == "test_work"
  92. assert failure.chapter_index == 5
  93. assert failure.error_type == "ValueError"
  94. assert failure.source_text == "Test content"
  95. # Check file was created
  96. assert manager.failure_list_path.exists()
  97. def test_load_failures(self, manager):
  98. """Test loading failures from file."""
  99. # Record some failures
  100. error1 = ValueError("Error 1")
  101. error2 = RuntimeError("Error 2")
  102. manager.record_failure("work1", 1, error1)
  103. manager.record_failure("work2", 2, error2)
  104. # Load them back
  105. failures = list(manager.load_failures())
  106. assert len(failures) == 2
  107. assert any(f.work_id == "work1" for f in failures)
  108. assert any(f.work_id == "work2" for f in failures)
  109. def test_load_failures_exclude_resolved(self, manager):
  110. """Test loading failures excluding resolved ones."""
  111. # Record failures
  112. error1 = ValueError("Error 1")
  113. error2 = RuntimeError("Error 2")
  114. manager.record_failure("work1", 1, error1)
  115. manager.record_failure("work2", 2, error2)
  116. # Mark one as resolved
  117. manager.mark_resolved("work1", 1)
  118. # Load excluding resolved
  119. failures = list(manager.load_failures(include_resolved=False))
  120. assert len(failures) == 1
  121. assert failures[0].work_id == "work2"
  122. def test_get_failures(self, manager):
  123. """Test getting failures as a list."""
  124. error = ValueError("Test error")
  125. manager.record_failure("test_work", 1, error)
  126. failures = manager.get_failures()
  127. assert len(failures) == 1
  128. assert failures[0].work_id == "test_work"
  129. def test_get_failures_for_work(self, manager):
  130. """Test getting failures for a specific work."""
  131. error = ValueError("Test error")
  132. manager.record_failure("work1", 1, error)
  133. manager.record_failure("work2", 2, error)
  134. work1_failures = manager.get_failures_for_work("work1")
  135. assert len(work1_failures) == 1
  136. assert work1_failures[0].work_id == "work1"
  137. def test_mark_resolved(self, manager):
  138. """Test marking a failure as resolved."""
  139. error = ValueError("Test error")
  140. manager.record_failure("test_work", 1, error)
  141. manager.mark_resolved("test_work", 1)
  142. # Load failures
  143. failures = manager.get_failures(include_resolved=True)
  144. assert len(failures) == 1
  145. assert failures[0].resolved is True
  146. def test_export_failure_list_jsonl(self, manager, temp_dir):
  147. """Test exporting failure list in JSONL format."""
  148. error = ValueError("Test error")
  149. manager.record_failure("test_work", 1, error)
  150. output_path = temp_dir / "export.jsonl"
  151. result = manager.export_failure_list(output_path, format="jsonl")
  152. assert result == output_path
  153. assert result.exists()
  154. # Verify content
  155. with open(result, "r") as f:
  156. lines = f.readlines()
  157. assert len(lines) == 1
  158. def test_export_failure_list_json(self, manager, temp_dir):
  159. """Test exporting failure list in JSON format."""
  160. error = ValueError("Test error")
  161. manager.record_failure("test_work", 1, error)
  162. output_path = temp_dir / "export.json"
  163. result = manager.export_failure_list(output_path, format="json")
  164. assert result == output_path
  165. assert result.exists()
  166. # Verify content
  167. with open(result, "r") as f:
  168. data = json.load(f)
  169. assert isinstance(data, list)
  170. assert len(data) == 1
  171. def test_export_failure_list_csv(self, manager, temp_dir):
  172. """Test exporting failure list in CSV format."""
  173. error = ValueError("Test error")
  174. manager.record_failure("test_work", 1, error)
  175. output_path = temp_dir / "export.csv"
  176. result = manager.export_failure_list(output_path, format="csv")
  177. assert result == output_path
  178. assert result.exists()
  179. def test_export_failure_list_unsupported_format(self, manager, temp_dir):
  180. """Test exporting with unsupported format."""
  181. error = ValueError("Test error")
  182. manager.record_failure("test_work", 1, error)
  183. with pytest.raises(ValueError, match="Unsupported format"):
  184. manager.export_failure_list(format="xml")
  185. def test_get_failure_summary(self, manager):
  186. """Test getting failure summary."""
  187. error1 = ValueError("Error 1")
  188. error2 = RuntimeError("Error 2")
  189. error3 = ValueError("Error 3")
  190. manager.record_failure("work1", 1, error1)
  191. manager.record_failure("work1", 2, error2)
  192. manager.record_failure("work2", 1, error3)
  193. summary = manager.get_failure_summary()
  194. assert summary["total_failures"] == 3
  195. assert summary["resolved_count"] == 0
  196. assert summary["unresolved_count"] == 3
  197. assert summary["by_error_type"]["ValueError"] == 2
  198. assert summary["by_error_type"]["RuntimeError"] == 1
  199. assert summary["by_work"]["work1"] == 2
  200. assert summary["by_work"]["work2"] == 1
  201. def test_get_retry_list(self, manager):
  202. """Test getting retry list."""
  203. error = ValueError("Test error")
  204. manager.record_failure("work1", 1, error)
  205. manager.record_failure("work2", 2, error)
  206. retry_list = manager.get_retry_list()
  207. assert len(retry_list) == 2
  208. def test_get_retry_list_for_work(self, manager):
  209. """Test getting retry list for specific work."""
  210. error = ValueError("Test error")
  211. manager.record_failure("work1", 1, error)
  212. manager.record_failure("work2", 2, error)
  213. retry_list = manager.get_retry_list(work_id="work1")
  214. assert len(retry_list) == 1
  215. assert retry_list[0].work_id == "work1"
  216. def test_increment_retry_count(self, manager):
  217. """Test incrementing retry count."""
  218. error = ValueError("Test error")
  219. manager.record_failure("test_work", 1, error)
  220. manager.increment_retry_count("test_work", 1)
  221. failures = manager.get_failures()
  222. assert failures[0].retry_count == 1
  223. def test_clear_resolved(self, manager):
  224. """Test clearing resolved failures."""
  225. error1 = ValueError("Error 1")
  226. error2 = RuntimeError("Error 2")
  227. manager.record_failure("work1", 1, error1)
  228. manager.record_failure("work2", 2, error2)
  229. # Mark one as resolved
  230. manager.mark_resolved("work1", 1)
  231. # Clear resolved
  232. removed = manager.clear_resolved()
  233. assert removed == 1
  234. # Only unresolved should remain
  235. failures = manager.get_failures(include_resolved=False)
  236. assert len(failures) == 1
  237. assert failures[0].work_id == "work2"
  238. def test_clear_all(self, manager):
  239. """Test clearing all failures."""
  240. error = ValueError("Test error")
  241. manager.record_failure("test_work", 1, error)
  242. assert manager.failure_list_path.exists()
  243. manager.clear_all()
  244. assert not manager.failure_list_path.exists()
  245. def test_empty_failure_list(self, manager):
  246. """Test operations on empty failure list."""
  247. failures = manager.get_failures()
  248. assert len(failures) == 0
  249. summary = manager.get_failure_summary()
  250. assert summary["total_failures"] == 0
  251. retry_list = manager.get_retry_list()
  252. assert len(retry_list) == 0
  253. def test_corrupted_line_handling(self, manager, temp_dir):
  254. """Test handling of corrupted lines in failure list."""
  255. # Record a valid failure
  256. error = ValueError("Test error")
  257. manager.record_failure("test_work", 1, error)
  258. # Add a corrupted line
  259. with open(manager.failure_list_path, "a") as f:
  260. f.write("this is not valid json\n")
  261. # Should skip corrupted line
  262. failures = manager.get_failures()
  263. assert len(failures) == 1
  264. assert failures[0].work_id == "test_work"