""" Tests for scorebug bug fixes (#39 and #40). #40: ScorecardTracker reads stale in-memory data — fix ensures get_scorecard() and get_all_scorecards() reload from disk before returning data. #39: Win percentage stuck at 50% — fix makes parsing robust for decimal (0.75), percentage string ("75%"), plain number ("75"), empty, and None values. When parsing fails, win_percentage is None instead of a misleading 50.0. """ import json import tempfile from pathlib import Path from unittest.mock import MagicMock import pytest from commands.gameplay.scorecard_tracker import ScorecardTracker from services.scorebug_service import ScorebugData from utils.scorebug_helpers import create_scorebug_embed, create_team_progress_bar class TestScorecardTrackerFreshReads: """Tests that ScorecardTracker reads fresh data from disk (fix for #40).""" @pytest.mark.asyncio async def test_get_all_scorecards_reads_fresh_data(self, tmp_path): """get_all_scorecards() should pick up scorecards written by another process. Simulates the background task having a stale tracker instance while the /publish-scorecard command writes new data to the JSON file. """ data_file = tmp_path / "scorecards.json" data_file.write_text(json.dumps({"scorecards": {}})) tracker = ScorecardTracker(data_file=str(data_file)) assert await tracker.get_all_scorecards() == [] # Another process writes a scorecard to the same file new_data = { "scorecards": { "111": { "text_channel_id": "111", "sheet_url": "https://docs.google.com/spreadsheets/d/abc123", "published_at": "2026-01-01T00:00:00", "last_updated": "2026-01-01T00:00:00", "publisher_id": "999", } } } data_file.write_text(json.dumps(new_data)) # Should see the new scorecard without restart result = await tracker.get_all_scorecards() assert len(result) == 1 assert result[0] == (111, "https://docs.google.com/spreadsheets/d/abc123") @pytest.mark.asyncio async def test_get_scorecard_reads_fresh_data(self, tmp_path): """get_scorecard() should pick up a scorecard written by another process.""" data_file = tmp_path / "scorecards.json" data_file.write_text(json.dumps({"scorecards": {}})) tracker = ScorecardTracker(data_file=str(data_file)) assert await tracker.get_scorecard(222) is None # Another process writes a scorecard new_data = { "scorecards": { "222": { "text_channel_id": "222", "sheet_url": "https://docs.google.com/spreadsheets/d/xyz789", "published_at": "2026-01-01T00:00:00", "last_updated": "2026-01-01T00:00:00", "publisher_id": "999", } } } data_file.write_text(json.dumps(new_data)) # Should see the new scorecard assert ( await tracker.get_scorecard(222) == "https://docs.google.com/spreadsheets/d/xyz789" ) class TestWinPercentageParsing: """Tests for robust win percentage parsing in ScorebugData (fix for #39).""" def test_percentage_string(self): """'75%' string should parse to 75.0.""" data = ScorebugData({"win_percentage": 75.0}) assert data.win_percentage == 75.0 def test_none_default(self): """Missing win_percentage key should default to None.""" data = ScorebugData({}) assert data.win_percentage is None def test_explicit_none(self): """Explicit None should stay None.""" data = ScorebugData({"win_percentage": None}) assert data.win_percentage is None def test_zero_is_valid(self): """0.0 win percentage is a valid value (team certain to lose).""" data = ScorebugData({"win_percentage": 0.0}) assert data.win_percentage == 0.0 class TestWinPercentageEmbed: """Tests for embed creation with win_percentage=None (fix for #39 Part B).""" def _make_scorebug_data(self, win_percentage): """Create minimal ScorebugData for embed testing.""" return ScorebugData( { "away_team_id": 1, "home_team_id": 2, "header": "Test Game", "away_score": 10, "home_score": 2, "which_half": "Top", "inning": 5, "is_final": False, "outs": 1, "win_percentage": win_percentage, "pitcher_name": "", "batter_name": "", "runners": [["", ""], ["", ""], ["", ""], ["", ""]], "summary": [], } ) def _make_team(self, abbrev, color_int=0x3498DB): """Create a mock team object.""" team = MagicMock() team.abbrev = abbrev team.get_color_int.return_value = color_int return team def test_embed_with_none_win_percentage_shows_unavailable(self): """When win_percentage is None, embed should show unavailable message.""" data = self._make_scorebug_data(win_percentage=None) away = self._make_team("POR") home = self._make_team("WV") embed = create_scorebug_embed(data, away, home, full_length=False) # Find the Win Probability field win_prob_field = next(f for f in embed.fields if f.name == "Win Probability") assert "unavailable" in win_prob_field.value.lower() def test_embed_with_valid_win_percentage_shows_bar(self): """When win_percentage is valid, embed should show the progress bar.""" data = self._make_scorebug_data(win_percentage=75.0) away = self._make_team("POR") home = self._make_team("WV") embed = create_scorebug_embed(data, away, home, full_length=False) win_prob_field = next(f for f in embed.fields if f.name == "Win Probability") assert "75.0%" in win_prob_field.value assert "unavailable" not in win_prob_field.value.lower() def test_embed_with_50_percent_shows_even_bar(self): """50% win probability should show the even/balanced bar.""" data = self._make_scorebug_data(win_percentage=50.0) away = self._make_team("POR") home = self._make_team("WV") embed = create_scorebug_embed(data, away, home, full_length=False) win_prob_field = next(f for f in embed.fields if f.name == "Win Probability") assert "50.0%" in win_prob_field.value assert "=" in win_prob_field.value