|
|
@@ -0,0 +1,336 @@
|
|
|
+"""
|
|
|
+Unit tests for the resume tracker module.
|
|
|
+
|
|
|
+Tests cover resume state tracking and loading.
|
|
|
+"""
|
|
|
+
|
|
|
+import sys
|
|
|
+from unittest.mock import Mock
|
|
|
+from pathlib import Path
|
|
|
+import json
|
|
|
+import tempfile
|
|
|
+
|
|
|
+# Mock torch and transformers before importing
|
|
|
+sys_mock = Mock()
|
|
|
+sys.modules["torch"] = sys_mock
|
|
|
+sys.modules["transformers"] = sys_mock
|
|
|
+
|
|
|
+import pytest
|
|
|
+
|
|
|
+from src.translator.resume_tracker import ResumeTracker, ResumeState
|
|
|
+
|
|
|
+
|
|
|
+class TestResumeState:
|
|
|
+ """Test cases for ResumeState dataclass."""
|
|
|
+
|
|
|
+ def test_create_resume_state(self):
|
|
|
+ """Test creating a resume state."""
|
|
|
+ state = ResumeState(
|
|
|
+ work_id="test_work",
|
|
|
+ last_completed_index=10,
|
|
|
+ last_failed_index=None,
|
|
|
+ total_chapters=100,
|
|
|
+ )
|
|
|
+
|
|
|
+ assert state.work_id == "test_work"
|
|
|
+ assert state.last_completed_index == 10
|
|
|
+ assert state.last_failed_index is None
|
|
|
+ assert state.total_chapters == 100
|
|
|
+
|
|
|
+ def test_to_dict(self):
|
|
|
+ """Test converting resume state to dictionary."""
|
|
|
+ state = ResumeState(
|
|
|
+ work_id="test_work",
|
|
|
+ last_completed_index=10,
|
|
|
+ last_failed_index=None,
|
|
|
+ total_chapters=100,
|
|
|
+ resume_message="Test message",
|
|
|
+ )
|
|
|
+
|
|
|
+ data = state.to_dict()
|
|
|
+
|
|
|
+ assert data["work_id"] == "test_work"
|
|
|
+ assert data["last_completed_index"] == 10
|
|
|
+ assert data["last_failed_index"] is None
|
|
|
+ assert data["total_chapters"] == 100
|
|
|
+ assert data["resume_message"] == "Test message"
|
|
|
+ assert "timestamp" in data
|
|
|
+
|
|
|
+ def test_from_dict(self):
|
|
|
+ """Test creating resume state from dictionary."""
|
|
|
+ data = {
|
|
|
+ "work_id": "test_work",
|
|
|
+ "last_completed_index": 10,
|
|
|
+ "last_failed_index": None,
|
|
|
+ "total_chapters": 100,
|
|
|
+ "timestamp": "2024-01-01T00:00:00",
|
|
|
+ "resume_message": "Test message",
|
|
|
+ }
|
|
|
+
|
|
|
+ state = ResumeState.from_dict(data)
|
|
|
+
|
|
|
+ assert state.work_id == "test_work"
|
|
|
+ assert state.last_completed_index == 10
|
|
|
+ assert state.total_chapters == 100
|
|
|
+
|
|
|
+ def test_get_resume_index_with_failure(self):
|
|
|
+ """Test getting resume index when there's a failed chapter."""
|
|
|
+ state = ResumeState(
|
|
|
+ work_id="test_work",
|
|
|
+ last_completed_index=10,
|
|
|
+ last_failed_index=15,
|
|
|
+ total_chapters=100,
|
|
|
+ )
|
|
|
+
|
|
|
+ assert state.get_resume_index() == 15
|
|
|
+
|
|
|
+ def test_get_resume_index_without_failure(self):
|
|
|
+ """Test getting resume index when there's no failed chapter."""
|
|
|
+ state = ResumeState(
|
|
|
+ work_id="test_work",
|
|
|
+ last_completed_index=10,
|
|
|
+ last_failed_index=None,
|
|
|
+ total_chapters=100,
|
|
|
+ )
|
|
|
+
|
|
|
+ assert state.get_resume_index() == 11
|
|
|
+
|
|
|
+ def test_is_resumable_with_remaining(self):
|
|
|
+ """Test is_resumable when there are remaining chapters."""
|
|
|
+ state = ResumeState(
|
|
|
+ work_id="test_work",
|
|
|
+ last_completed_index=10,
|
|
|
+ last_failed_index=None,
|
|
|
+ total_chapters=100,
|
|
|
+ )
|
|
|
+
|
|
|
+ assert state.is_resumable() is True
|
|
|
+
|
|
|
+ def test_is_resumable_complete(self):
|
|
|
+ """Test is_resumable when all chapters are complete."""
|
|
|
+ state = ResumeState(
|
|
|
+ work_id="test_work",
|
|
|
+ last_completed_index=99,
|
|
|
+ last_failed_index=None,
|
|
|
+ total_chapters=100,
|
|
|
+ )
|
|
|
+
|
|
|
+ assert state.is_resumable() is False
|
|
|
+
|
|
|
+
|
|
|
+class TestResumeTracker:
|
|
|
+ """Test cases for ResumeTracker class."""
|
|
|
+
|
|
|
+ @pytest.fixture
|
|
|
+ def temp_dir(self):
|
|
|
+ """Create a temporary directory for testing."""
|
|
|
+ with tempfile.TemporaryDirectory() as tmp:
|
|
|
+ yield Path(tmp)
|
|
|
+
|
|
|
+ @pytest.fixture
|
|
|
+ def tracker(self, temp_dir):
|
|
|
+ """Create a resume tracker for testing."""
|
|
|
+ return ResumeTracker(temp_dir)
|
|
|
+
|
|
|
+ def test_init(self, temp_dir):
|
|
|
+ """Test ResumeTracker initialization."""
|
|
|
+ tracker = ResumeTracker(temp_dir)
|
|
|
+
|
|
|
+ assert tracker.storage_dir == temp_dir
|
|
|
+ assert tracker.resume_dir == temp_dir / "resume"
|
|
|
+ assert tracker.resume_dir.exists()
|
|
|
+
|
|
|
+ def test_save_resume_state(self, tracker):
|
|
|
+ """Test saving resume state."""
|
|
|
+ state = tracker.save_resume_state(
|
|
|
+ work_id="test_work",
|
|
|
+ last_completed_index=10,
|
|
|
+ last_failed_index=None,
|
|
|
+ total_chapters=100,
|
|
|
+ )
|
|
|
+
|
|
|
+ assert state.work_id == "test_work"
|
|
|
+ assert state.last_completed_index == 10
|
|
|
+ assert "Resume from chapter" in state.resume_message
|
|
|
+
|
|
|
+ # Check file was created
|
|
|
+ resume_file = tracker._get_resume_file_path("test_work")
|
|
|
+ assert resume_file.exists()
|
|
|
+
|
|
|
+ def test_load_resume_state(self, tracker):
|
|
|
+ """Test loading resume state."""
|
|
|
+ # Save state first
|
|
|
+ tracker.save_resume_state(
|
|
|
+ work_id="test_work",
|
|
|
+ last_completed_index=10,
|
|
|
+ last_failed_index=None,
|
|
|
+ total_chapters=100,
|
|
|
+ )
|
|
|
+
|
|
|
+ # Load it back
|
|
|
+ loaded = tracker.load_resume_state("test_work")
|
|
|
+
|
|
|
+ assert loaded is not None
|
|
|
+ assert loaded.work_id == "test_work"
|
|
|
+ assert loaded.last_completed_index == 10
|
|
|
+ assert loaded.total_chapters == 100
|
|
|
+
|
|
|
+ def test_load_nonexistent_resume_state(self, tracker):
|
|
|
+ """Test loading a non-existent resume state."""
|
|
|
+ loaded = tracker.load_resume_state("nonexistent")
|
|
|
+ assert loaded is None
|
|
|
+
|
|
|
+ def test_delete_resume_state(self, tracker):
|
|
|
+ """Test deleting resume state."""
|
|
|
+ # Save state first
|
|
|
+ tracker.save_resume_state(
|
|
|
+ work_id="test_work",
|
|
|
+ last_completed_index=10,
|
|
|
+ last_failed_index=None,
|
|
|
+ total_chapters=100,
|
|
|
+ )
|
|
|
+
|
|
|
+ # Delete it
|
|
|
+ tracker.delete_resume_state("test_work")
|
|
|
+
|
|
|
+ # Check it's gone
|
|
|
+ resume_file = tracker._get_resume_file_path("test_work")
|
|
|
+ assert not resume_file.exists()
|
|
|
+
|
|
|
+ def test_get_resume_index(self, tracker):
|
|
|
+ """Test getting resume index."""
|
|
|
+ # Save state first
|
|
|
+ tracker.save_resume_state(
|
|
|
+ work_id="test_work",
|
|
|
+ last_completed_index=10,
|
|
|
+ last_failed_index=None,
|
|
|
+ total_chapters=100,
|
|
|
+ )
|
|
|
+
|
|
|
+ # Get resume index
|
|
|
+ index = tracker.get_resume_index("test_work", 100)
|
|
|
+
|
|
|
+ assert index == 11
|
|
|
+
|
|
|
+ def test_get_resume_index_nonexistent(self, tracker):
|
|
|
+ """Test getting resume index for non-existent state."""
|
|
|
+ index = tracker.get_resume_index("nonexistent", 100)
|
|
|
+ assert index is None
|
|
|
+
|
|
|
+ def test_update_on_chapter_complete(self, tracker):
|
|
|
+ """Test updating state on chapter completion."""
|
|
|
+ tracker.update_on_chapter_complete(
|
|
|
+ work_id="test_work",
|
|
|
+ chapter_index=5,
|
|
|
+ total_chapters=100,
|
|
|
+ )
|
|
|
+
|
|
|
+ loaded = tracker.load_resume_state("test_work")
|
|
|
+ assert loaded is not None
|
|
|
+ assert loaded.last_completed_index == 5
|
|
|
+ assert loaded.last_failed_index is None
|
|
|
+
|
|
|
+ def test_update_on_chapter_failed(self, tracker):
|
|
|
+ """Test updating state on chapter failure."""
|
|
|
+ tracker.update_on_chapter_failed(
|
|
|
+ work_id="test_work",
|
|
|
+ chapter_index=5,
|
|
|
+ total_chapters=100,
|
|
|
+ )
|
|
|
+
|
|
|
+ loaded = tracker.load_resume_state("test_work")
|
|
|
+ assert loaded is not None
|
|
|
+ assert loaded.last_failed_index == 5
|
|
|
+
|
|
|
+ def test_generate_resume_message_with_failure(self):
|
|
|
+ """Test generating resume message with failed chapter."""
|
|
|
+ state = ResumeState(
|
|
|
+ work_id="test_work",
|
|
|
+ last_completed_index=10,
|
|
|
+ last_failed_index=15,
|
|
|
+ total_chapters=100,
|
|
|
+ )
|
|
|
+
|
|
|
+ tracker = ResumeTracker(Path("/tmp"))
|
|
|
+ message = tracker._generate_resume_message(state)
|
|
|
+
|
|
|
+ assert "failed chapter 15" in message
|
|
|
+ assert "85" in message # remaining chapters
|
|
|
+
|
|
|
+ def test_generate_resume_message_without_failure(self):
|
|
|
+ """Test generating resume message without failed chapter."""
|
|
|
+ state = ResumeState(
|
|
|
+ work_id="test_work",
|
|
|
+ last_completed_index=10,
|
|
|
+ last_failed_index=None,
|
|
|
+ total_chapters=100,
|
|
|
+ )
|
|
|
+
|
|
|
+ tracker = ResumeTracker(Path("/tmp"))
|
|
|
+ message = tracker._generate_resume_message(state)
|
|
|
+
|
|
|
+ assert "Resume from chapter 11" in message
|
|
|
+ assert "89" in message # remaining chapters
|
|
|
+
|
|
|
+ def test_get_all_resume_states(self, tracker):
|
|
|
+ """Test getting all resume states."""
|
|
|
+ # Save multiple states
|
|
|
+ tracker.save_resume_state("work1", 10, None, 100)
|
|
|
+ tracker.save_resume_state("work2", 20, None, 200)
|
|
|
+
|
|
|
+ states = tracker.get_all_resume_states()
|
|
|
+
|
|
|
+ assert len(states) == 2
|
|
|
+ work_ids = {s.work_id for s in states}
|
|
|
+ assert work_ids == {"work1", "work2"}
|
|
|
+
|
|
|
+ def test_clear_all_resume_states(self, tracker):
|
|
|
+ """Test clearing all resume states."""
|
|
|
+ # Save multiple states
|
|
|
+ tracker.save_resume_state("work1", 10, None, 100)
|
|
|
+ tracker.save_resume_state("work2", 20, None, 200)
|
|
|
+
|
|
|
+ tracker.clear_all_resume_states()
|
|
|
+
|
|
|
+ states = tracker.get_all_resume_states()
|
|
|
+ assert len(states) == 0
|
|
|
+
|
|
|
+ def test_update_existing_state_on_complete(self, tracker):
|
|
|
+ """Test that updating state preserves existing data."""
|
|
|
+ # Save initial state
|
|
|
+ tracker.save_resume_state(
|
|
|
+ work_id="test_work",
|
|
|
+ last_completed_index=5,
|
|
|
+ last_failed_index=7,
|
|
|
+ total_chapters=100,
|
|
|
+ )
|
|
|
+
|
|
|
+ # Update on completion
|
|
|
+ tracker.update_on_chapter_complete(
|
|
|
+ work_id="test_work",
|
|
|
+ chapter_index=10,
|
|
|
+ total_chapters=100,
|
|
|
+ )
|
|
|
+
|
|
|
+ loaded = tracker.load_resume_state("test_work")
|
|
|
+ assert loaded.last_completed_index == 10
|
|
|
+ assert loaded.last_failed_index is None # Should be cleared
|
|
|
+
|
|
|
+ def test_custom_resume_message(self, tracker):
|
|
|
+ """Test saving state with custom resume message."""
|
|
|
+ state = tracker.save_resume_state(
|
|
|
+ work_id="test_work",
|
|
|
+ last_completed_index=10,
|
|
|
+ last_failed_index=None,
|
|
|
+ total_chapters=100,
|
|
|
+ resume_message="Custom message here",
|
|
|
+ )
|
|
|
+
|
|
|
+ assert state.resume_message == "Custom message here"
|
|
|
+
|
|
|
+ def test_get_resume_file_path(self, tracker):
|
|
|
+ """Test getting resume file path."""
|
|
|
+ path = tracker._get_resume_file_path("test_work")
|
|
|
+
|
|
|
+ assert path.name == "test_work_resume.json"
|
|
|
+ assert path.parent == tracker.resume_dir
|