major-domo-v2/tests/test_scorebug_bugs.py
Cal Corum d1a6b57ccd
All checks were successful
Build Docker Image / build (pull_request) Successful in 1m14s
fix: scorebug stale data, win probability parsing, and read-failure tolerance (closes #39, #40)
#40: ScorecardTracker cached data in memory at startup — background task never
saw newly published scorecards. Fixed by reloading from disk on every read.

#39: Win percentage defaulted to 50% when unavailable, showing a misleading
50/50 bar. Now defaults to None with "unavailable" message in embed. Parsing
handles decimal (0.75), percentage string, and empty values. Also fixed
orientation bug where win% was always shown as home team's even when the
sheet reports the away team as the leader.

Additionally: live scorebug tracker now distinguishes between "all games
confirmed final" and "sheet read failures" — transient Google Sheets errors
no longer hide the live scores channel.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 14:14:23 -06:00

177 lines
6.5 KiB
Python

"""
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)."""
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 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 = tracker.get_all_scorecards()
assert len(result) == 1
assert result[0] == (111, "https://docs.google.com/spreadsheets/d/abc123")
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 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 (
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