"""Tests for the upload state manager.""" import json import pytest from datetime import datetime, timedelta from pathlib import Path from tempfile import TemporaryDirectory from sportstime_parser.uploaders.state import ( RecordState, UploadSession, StateManager, ) class TestRecordState: """Tests for RecordState dataclass.""" def test_create_record_state(self): """Test creating a RecordState with default values.""" state = RecordState( record_name="nba_2025_hou_okc_1021", record_type="Game", ) assert state.record_name == "nba_2025_hou_okc_1021" assert state.record_type == "Game" assert state.status == "pending" assert state.uploaded_at is None assert state.record_change_tag is None assert state.error_message is None assert state.retry_count == 0 def test_record_state_to_dict(self): """Test serializing RecordState to dictionary.""" now = datetime.utcnow() state = RecordState( record_name="nba_2025_hou_okc_1021", record_type="Game", uploaded_at=now, record_change_tag="abc123", status="uploaded", ) data = state.to_dict() assert data["record_name"] == "nba_2025_hou_okc_1021" assert data["record_type"] == "Game" assert data["status"] == "uploaded" assert data["uploaded_at"] == now.isoformat() assert data["record_change_tag"] == "abc123" def test_record_state_from_dict(self): """Test deserializing RecordState from dictionary.""" data = { "record_name": "nba_2025_hou_okc_1021", "record_type": "Game", "uploaded_at": "2026-01-10T12:00:00", "record_change_tag": "abc123", "status": "uploaded", "error_message": None, "retry_count": 0, } state = RecordState.from_dict(data) assert state.record_name == "nba_2025_hou_okc_1021" assert state.record_type == "Game" assert state.status == "uploaded" assert state.uploaded_at == datetime.fromisoformat("2026-01-10T12:00:00") assert state.record_change_tag == "abc123" class TestUploadSession: """Tests for UploadSession dataclass.""" def test_create_upload_session(self): """Test creating an UploadSession.""" session = UploadSession( sport="nba", season=2025, environment="development", ) assert session.sport == "nba" assert session.season == 2025 assert session.environment == "development" assert session.total_count == 0 assert len(session.records) == 0 def test_add_record(self): """Test adding records to a session.""" session = UploadSession( sport="nba", season=2025, environment="development", ) session.add_record("game_1", "Game") session.add_record("game_2", "Game") session.add_record("team_1", "Team") assert session.total_count == 3 assert len(session.records) == 3 assert "game_1" in session.records assert session.records["game_1"].record_type == "Game" def test_mark_uploaded(self): """Test marking a record as uploaded.""" session = UploadSession( sport="nba", season=2025, environment="development", ) session.add_record("game_1", "Game") session.mark_uploaded("game_1", "change_tag_123") assert session.records["game_1"].status == "uploaded" assert session.records["game_1"].record_change_tag == "change_tag_123" assert session.records["game_1"].uploaded_at is not None def test_mark_failed(self): """Test marking a record as failed.""" session = UploadSession( sport="nba", season=2025, environment="development", ) session.add_record("game_1", "Game") session.mark_failed("game_1", "Server error") assert session.records["game_1"].status == "failed" assert session.records["game_1"].error_message == "Server error" assert session.records["game_1"].retry_count == 1 def test_mark_failed_increments_retry_count(self): """Test that marking failed increments retry count.""" session = UploadSession( sport="nba", season=2025, environment="development", ) session.add_record("game_1", "Game") session.mark_failed("game_1", "Error 1") session.mark_failed("game_1", "Error 2") session.mark_failed("game_1", "Error 3") assert session.records["game_1"].retry_count == 3 def test_counts(self): """Test session counts.""" session = UploadSession( sport="nba", season=2025, environment="development", ) session.add_record("game_1", "Game") session.add_record("game_2", "Game") session.add_record("game_3", "Game") session.mark_uploaded("game_1") session.mark_failed("game_2", "Error") assert session.uploaded_count == 1 assert session.failed_count == 1 assert session.pending_count == 1 def test_is_complete(self): """Test is_complete property.""" session = UploadSession( sport="nba", season=2025, environment="development", ) session.add_record("game_1", "Game") session.add_record("game_2", "Game") assert not session.is_complete session.mark_uploaded("game_1") assert not session.is_complete session.mark_uploaded("game_2") assert session.is_complete def test_progress_percent(self): """Test progress percentage calculation.""" session = UploadSession( sport="nba", season=2025, environment="development", ) session.add_record("game_1", "Game") session.add_record("game_2", "Game") session.add_record("game_3", "Game") session.add_record("game_4", "Game") session.mark_uploaded("game_1") assert session.progress_percent == 25.0 def test_get_pending_records(self): """Test getting pending record names.""" session = UploadSession( sport="nba", season=2025, environment="development", ) session.add_record("game_1", "Game") session.add_record("game_2", "Game") session.add_record("game_3", "Game") session.mark_uploaded("game_1") session.mark_failed("game_2", "Error") pending = session.get_pending_records() assert pending == ["game_3"] def test_get_failed_records(self): """Test getting failed record names.""" session = UploadSession( sport="nba", season=2025, environment="development", ) session.add_record("game_1", "Game") session.add_record("game_2", "Game") session.add_record("game_3", "Game") session.mark_failed("game_1", "Error 1") session.mark_failed("game_3", "Error 3") failed = session.get_failed_records() assert set(failed) == {"game_1", "game_3"} def test_get_retryable_records(self): """Test getting records eligible for retry.""" session = UploadSession( sport="nba", season=2025, environment="development", ) session.add_record("game_1", "Game") session.add_record("game_2", "Game") session.add_record("game_3", "Game") # Fail game_1 once session.mark_failed("game_1", "Error") # Fail game_2 three times (max retries) session.mark_failed("game_2", "Error") session.mark_failed("game_2", "Error") session.mark_failed("game_2", "Error") retryable = session.get_retryable_records(max_retries=3) assert retryable == ["game_1"] def test_to_dict_and_from_dict(self): """Test round-trip serialization.""" session = UploadSession( sport="nba", season=2025, environment="development", ) session.add_record("game_1", "Game") session.add_record("game_2", "Game") session.mark_uploaded("game_1", "tag_123") data = session.to_dict() restored = UploadSession.from_dict(data) assert restored.sport == session.sport assert restored.season == session.season assert restored.environment == session.environment assert restored.total_count == session.total_count assert restored.uploaded_count == session.uploaded_count assert restored.records["game_1"].status == "uploaded" class TestStateManager: """Tests for StateManager.""" def test_create_session(self): """Test creating a new session.""" with TemporaryDirectory() as tmpdir: manager = StateManager(state_dir=Path(tmpdir)) session = manager.create_session( sport="nba", season=2025, environment="development", record_names=[ ("game_1", "Game"), ("game_2", "Game"), ("team_1", "Team"), ], ) assert session.sport == "nba" assert session.season == 2025 assert session.total_count == 3 # Check file was created state_file = Path(tmpdir) / "upload_state_nba_2025_development.json" assert state_file.exists() def test_load_session(self): """Test loading an existing session.""" with TemporaryDirectory() as tmpdir: manager = StateManager(state_dir=Path(tmpdir)) # Create and save a session original = manager.create_session( sport="nba", season=2025, environment="development", record_names=[("game_1", "Game")], ) original.mark_uploaded("game_1", "tag_123") manager.save_session(original) # Load it back loaded = manager.load_session("nba", 2025, "development") assert loaded is not None assert loaded.sport == "nba" assert loaded.records["game_1"].status == "uploaded" def test_load_nonexistent_session(self): """Test loading a session that doesn't exist.""" with TemporaryDirectory() as tmpdir: manager = StateManager(state_dir=Path(tmpdir)) session = manager.load_session("nba", 2025, "development") assert session is None def test_delete_session(self): """Test deleting a session.""" with TemporaryDirectory() as tmpdir: manager = StateManager(state_dir=Path(tmpdir)) # Create a session manager.create_session( sport="nba", season=2025, environment="development", record_names=[("game_1", "Game")], ) # Delete it result = manager.delete_session("nba", 2025, "development") assert result is True # Verify it's gone loaded = manager.load_session("nba", 2025, "development") assert loaded is None def test_delete_nonexistent_session(self): """Test deleting a session that doesn't exist.""" with TemporaryDirectory() as tmpdir: manager = StateManager(state_dir=Path(tmpdir)) result = manager.delete_session("nba", 2025, "development") assert result is False def test_list_sessions(self): """Test listing all sessions.""" with TemporaryDirectory() as tmpdir: manager = StateManager(state_dir=Path(tmpdir)) # Create multiple sessions manager.create_session( sport="nba", season=2025, environment="development", record_names=[("game_1", "Game")], ) manager.create_session( sport="mlb", season=2026, environment="production", record_names=[("game_2", "Game"), ("game_3", "Game")], ) sessions = manager.list_sessions() assert len(sessions) == 2 sports = {s["sport"] for s in sessions} assert sports == {"nba", "mlb"} def test_get_session_or_create_new(self): """Test getting a session when none exists.""" with TemporaryDirectory() as tmpdir: manager = StateManager(state_dir=Path(tmpdir)) session = manager.get_session_or_create( sport="nba", season=2025, environment="development", record_names=[("game_1", "Game")], resume=False, ) assert session.sport == "nba" assert session.total_count == 1 def test_get_session_or_create_resume(self): """Test resuming an existing session.""" with TemporaryDirectory() as tmpdir: manager = StateManager(state_dir=Path(tmpdir)) # Create initial session original = manager.create_session( sport="nba", season=2025, environment="development", record_names=[("game_1", "Game"), ("game_2", "Game")], ) original.mark_uploaded("game_1", "tag_123") manager.save_session(original) # Resume with additional records session = manager.get_session_or_create( sport="nba", season=2025, environment="development", record_names=[("game_1", "Game"), ("game_2", "Game"), ("game_3", "Game")], resume=True, ) # Should have original progress plus new record assert session.records["game_1"].status == "uploaded" assert "game_3" in session.records assert session.total_count == 3 def test_get_session_or_create_overwrite(self): """Test overwriting an existing session when not resuming.""" with TemporaryDirectory() as tmpdir: manager = StateManager(state_dir=Path(tmpdir)) # Create initial session original = manager.create_session( sport="nba", season=2025, environment="development", record_names=[("game_1", "Game"), ("game_2", "Game")], ) original.mark_uploaded("game_1", "tag_123") manager.save_session(original) # Create new session (not resuming) session = manager.get_session_or_create( sport="nba", season=2025, environment="development", record_names=[("game_3", "Game")], resume=False, ) # Should be a fresh session assert session.total_count == 1 assert "game_1" not in session.records assert "game_3" in session.records