"""Tests for the record differ.""" import pytest from datetime import datetime from sportstime_parser.models.game import Game from sportstime_parser.models.team import Team from sportstime_parser.models.stadium import Stadium from sportstime_parser.uploaders.diff import ( DiffAction, RecordDiff, DiffResult, RecordDiffer, game_to_cloudkit_record, team_to_cloudkit_record, stadium_to_cloudkit_record, ) from sportstime_parser.uploaders.cloudkit import RecordType class TestRecordDiff: """Tests for RecordDiff dataclass.""" def test_create_record_diff(self): """Test creating a RecordDiff.""" diff = RecordDiff( record_name="nba_2025_hou_okc_1021", record_type=RecordType.GAME, action=DiffAction.CREATE, ) assert diff.record_name == "nba_2025_hou_okc_1021" assert diff.record_type == RecordType.GAME assert diff.action == DiffAction.CREATE class TestDiffResult: """Tests for DiffResult dataclass.""" def test_empty_result(self): """Test empty DiffResult.""" result = DiffResult() assert result.create_count == 0 assert result.update_count == 0 assert result.delete_count == 0 assert result.unchanged_count == 0 assert result.total_changes == 0 def test_counts(self): """Test counting different change types.""" result = DiffResult() result.creates.append(RecordDiff( record_name="game_1", record_type=RecordType.GAME, action=DiffAction.CREATE, )) result.creates.append(RecordDiff( record_name="game_2", record_type=RecordType.GAME, action=DiffAction.CREATE, )) result.updates.append(RecordDiff( record_name="game_3", record_type=RecordType.GAME, action=DiffAction.UPDATE, )) result.deletes.append(RecordDiff( record_name="game_4", record_type=RecordType.GAME, action=DiffAction.DELETE, )) result.unchanged.append(RecordDiff( record_name="game_5", record_type=RecordType.GAME, action=DiffAction.UNCHANGED, )) assert result.create_count == 2 assert result.update_count == 1 assert result.delete_count == 1 assert result.unchanged_count == 1 assert result.total_changes == 4 # excludes unchanged class TestRecordDiffer: """Tests for RecordDiffer.""" @pytest.fixture def differ(self): """Create a RecordDiffer instance.""" return RecordDiffer() @pytest.fixture def sample_game(self): """Create a sample Game.""" return Game( id="nba_2025_hou_okc_1021", sport="nba", season=2025, home_team_id="team_nba_okc", away_team_id="team_nba_hou", stadium_id="stadium_nba_paycom_center", game_date=datetime(2025, 10, 21, 19, 0, 0), status="scheduled", ) @pytest.fixture def sample_team(self): """Create a sample Team.""" return Team( id="team_nba_okc", sport="nba", city="Oklahoma City", name="Thunder", full_name="Oklahoma City Thunder", abbreviation="OKC", conference="Western", division="Northwest", ) @pytest.fixture def sample_stadium(self): """Create a sample Stadium.""" return Stadium( id="stadium_nba_paycom_center", sport="nba", name="Paycom Center", city="Oklahoma City", state="OK", country="USA", latitude=35.4634, longitude=-97.5151, capacity=18203, ) def test_diff_games_create(self, differ, sample_game): """Test detecting new games to create.""" local_games = [sample_game] remote_records = [] result = differ.diff_games(local_games, remote_records) assert result.create_count == 1 assert result.update_count == 0 assert result.delete_count == 0 assert result.creates[0].record_name == sample_game.id def test_diff_games_delete(self, differ, sample_game): """Test detecting games to delete.""" local_games = [] remote_records = [ { "recordName": sample_game.id, "recordType": "Game", "fields": { "sport": {"value": "nba", "type": "STRING"}, "season": {"value": 2025, "type": "INT64"}, }, "recordChangeTag": "abc123", } ] result = differ.diff_games(local_games, remote_records) assert result.create_count == 0 assert result.delete_count == 1 assert result.deletes[0].record_name == sample_game.id def test_diff_games_unchanged(self, differ, sample_game): """Test detecting unchanged games.""" local_games = [sample_game] remote_records = [ { "recordName": sample_game.id, "recordType": "Game", "fields": { "sport": {"value": "nba", "type": "STRING"}, "season": {"value": 2025, "type": "INT64"}, "home_team_id": {"value": "team_nba_okc", "type": "STRING"}, "away_team_id": {"value": "team_nba_hou", "type": "STRING"}, "stadium_id": {"value": "stadium_nba_paycom_center", "type": "STRING"}, "game_date": {"value": int(sample_game.game_date.timestamp() * 1000), "type": "TIMESTAMP"}, "game_number": {"value": None, "type": "INT64"}, "home_score": {"value": None, "type": "INT64"}, "away_score": {"value": None, "type": "INT64"}, "status": {"value": "scheduled", "type": "STRING"}, }, "recordChangeTag": "abc123", } ] result = differ.diff_games(local_games, remote_records) assert result.create_count == 0 assert result.update_count == 0 assert result.unchanged_count == 1 def test_diff_games_update(self, differ, sample_game): """Test detecting games that need update.""" local_games = [sample_game] # Remote has different status remote_records = [ { "recordName": sample_game.id, "recordType": "Game", "fields": { "sport": {"value": "nba", "type": "STRING"}, "season": {"value": 2025, "type": "INT64"}, "home_team_id": {"value": "team_nba_okc", "type": "STRING"}, "away_team_id": {"value": "team_nba_hou", "type": "STRING"}, "stadium_id": {"value": "stadium_nba_paycom_center", "type": "STRING"}, "game_date": {"value": int(sample_game.game_date.timestamp() * 1000), "type": "TIMESTAMP"}, "game_number": {"value": None, "type": "INT64"}, "home_score": {"value": None, "type": "INT64"}, "away_score": {"value": None, "type": "INT64"}, "status": {"value": "postponed", "type": "STRING"}, # Different! }, "recordChangeTag": "abc123", } ] result = differ.diff_games(local_games, remote_records) assert result.update_count == 1 assert "status" in result.updates[0].changed_fields assert result.updates[0].record_change_tag == "abc123" def test_diff_teams_create(self, differ, sample_team): """Test detecting new teams to create.""" local_teams = [sample_team] remote_records = [] result = differ.diff_teams(local_teams, remote_records) assert result.create_count == 1 assert result.creates[0].record_name == sample_team.id def test_diff_stadiums_create(self, differ, sample_stadium): """Test detecting new stadiums to create.""" local_stadiums = [sample_stadium] remote_records = [] result = differ.diff_stadiums(local_stadiums, remote_records) assert result.create_count == 1 assert result.creates[0].record_name == sample_stadium.id def test_get_records_to_upload(self, differ, sample_game): """Test getting CloudKitRecords for upload.""" game2 = Game( id="nba_2025_lal_lac_1022", sport="nba", season=2025, home_team_id="team_nba_lac", away_team_id="team_nba_lal", stadium_id="stadium_nba_crypto_com", game_date=datetime(2025, 10, 22, 19, 0, 0), status="scheduled", ) local_games = [sample_game, game2] # Only game2 exists remotely with different status remote_records = [ { "recordName": game2.id, "recordType": "Game", "fields": { "sport": {"value": "nba", "type": "STRING"}, "season": {"value": 2025, "type": "INT64"}, "home_team_id": {"value": "team_nba_lac", "type": "STRING"}, "away_team_id": {"value": "team_nba_lal", "type": "STRING"}, "stadium_id": {"value": "stadium_nba_crypto_com", "type": "STRING"}, "game_date": {"value": int(game2.game_date.timestamp() * 1000), "type": "TIMESTAMP"}, "status": {"value": "postponed", "type": "STRING"}, # Different! }, "recordChangeTag": "xyz789", } ] result = differ.diff_games(local_games, remote_records) records = result.get_records_to_upload() assert len(records) == 2 # 1 create + 1 update record_names = [r.record_name for r in records] assert sample_game.id in record_names assert game2.id in record_names class TestConvenienceFunctions: """Tests for module-level convenience functions.""" def test_game_to_cloudkit_record(self): """Test converting Game to CloudKitRecord.""" game = Game( id="nba_2025_hou_okc_1021", sport="nba", season=2025, home_team_id="team_nba_okc", away_team_id="team_nba_hou", stadium_id="stadium_nba_paycom_center", game_date=datetime(2025, 10, 21, 19, 0, 0), status="scheduled", ) record = game_to_cloudkit_record(game) assert record.record_name == game.id assert record.record_type == RecordType.GAME assert record.fields["sport"] == "nba" assert record.fields["season"] == 2025 def test_team_to_cloudkit_record(self): """Test converting Team to CloudKitRecord.""" team = Team( id="team_nba_okc", sport="nba", city="Oklahoma City", name="Thunder", full_name="Oklahoma City Thunder", abbreviation="OKC", ) record = team_to_cloudkit_record(team) assert record.record_name == team.id assert record.record_type == RecordType.TEAM assert record.fields["city"] == "Oklahoma City" assert record.fields["name"] == "Thunder" def test_stadium_to_cloudkit_record(self): """Test converting Stadium to CloudKitRecord.""" stadium = Stadium( id="stadium_nba_paycom_center", sport="nba", name="Paycom Center", city="Oklahoma City", state="OK", country="USA", latitude=35.4634, longitude=-97.5151, ) record = stadium_to_cloudkit_record(stadium) assert record.record_name == stadium.id assert record.record_type == RecordType.STADIUM assert record.fields["name"] == "Paycom Center" assert record.fields["latitude"] == 35.4634