mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-05-24 05:13:33 +00:00
Adds test coverage for six major untested areas: - src/base_classes/api_extractors.py — ESPN football, baseball, hockey, soccer extractors - src/base_classes/data_sources.py — ESPN, MLB, and soccer API data sources (HTTP mocked) - src/common/game_helper.py — game extraction, filtering, sorting, and summaries - src/common/utils.py — all utility functions (normalise, format, validate, parse) - src/common/scroll_helper.py — ScrollHelper init, create, update, visible portion, duration - src/background_data_service.py — cache hit/miss paths, retry, cancel, cleanup, singleton - src/vegas_mode/config.py — VegasModeConfig from_config, validate, update, ordering - src/logo_downloader.py — normalize_abbreviation, filename variations, directory helpers - src/plugin_system/health_monitor.py — HealthStatus determination, metrics, suggestions, lifecycle https://claude.ai/code/session_015792DiGo27JbgH5mk3KBjk
318 lines
11 KiB
Python
318 lines
11 KiB
Python
"""
|
|
Tests for src/common/game_helper.py
|
|
|
|
Covers GameHelper: extract_game_details, filter_*, sort_games_by_time,
|
|
process_games, get_game_summary, and all private helpers.
|
|
"""
|
|
|
|
import logging
|
|
import pytest
|
|
from datetime import datetime, timezone, timedelta
|
|
|
|
from src.common.game_helper import GameHelper
|
|
|
|
|
|
def _make_logger() -> logging.Logger:
|
|
return logging.getLogger("test_game_helper")
|
|
|
|
|
|
def _make_espn_event(
|
|
state: str = "in",
|
|
home_abbr: str = "LAL",
|
|
away_abbr: str = "BOS",
|
|
home_score: str = "105",
|
|
away_score: str = "98",
|
|
date_str: str = "2024-01-15T20:00:00Z",
|
|
period: int = 4,
|
|
status_name: str = "STATUS_IN_PROGRESS",
|
|
home_record: str = "30-10",
|
|
away_record: str = "25-15",
|
|
event_id: str = "game-1",
|
|
) -> dict:
|
|
return {
|
|
"id": event_id,
|
|
"date": date_str,
|
|
"competitions": [
|
|
{
|
|
"status": {
|
|
"type": {
|
|
"state": state,
|
|
"shortDetail": "Q4 2:30",
|
|
"name": status_name,
|
|
},
|
|
"period": period,
|
|
"displayClock": "2:30",
|
|
},
|
|
"competitors": [
|
|
{
|
|
"homeAway": "home",
|
|
"id": "h1",
|
|
"team": {"abbreviation": home_abbr, "displayName": f"{home_abbr} Team"},
|
|
"score": home_score,
|
|
"records": [{"summary": home_record}],
|
|
},
|
|
{
|
|
"homeAway": "away",
|
|
"id": "a1",
|
|
"team": {"abbreviation": away_abbr, "displayName": f"{away_abbr} Team"},
|
|
"score": away_score,
|
|
"records": [{"summary": away_record}],
|
|
},
|
|
],
|
|
}
|
|
],
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def helper():
|
|
return GameHelper(timezone_str="UTC", logger=_make_logger())
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# extract_game_details
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestExtractGameDetails:
|
|
def test_live_game(self, helper):
|
|
event = _make_espn_event(state="in")
|
|
result = helper.extract_game_details(event)
|
|
assert result is not None
|
|
assert result["is_live"] is True
|
|
assert result["is_final"] is False
|
|
assert result["is_upcoming"] is False
|
|
|
|
def test_final_game(self, helper):
|
|
event = _make_espn_event(state="post")
|
|
result = helper.extract_game_details(event)
|
|
assert result["is_final"] is True
|
|
|
|
def test_upcoming_game(self, helper):
|
|
event = _make_espn_event(state="pre")
|
|
result = helper.extract_game_details(event)
|
|
assert result["is_upcoming"] is True
|
|
|
|
def test_halftime_detection(self, helper):
|
|
event = _make_espn_event(state="halftime", status_name="STATUS_HALFTIME")
|
|
result = helper.extract_game_details(event)
|
|
assert result["is_halftime"] is True
|
|
|
|
def test_basic_fields_present(self, helper):
|
|
event = _make_espn_event()
|
|
result = helper.extract_game_details(event)
|
|
for key in ("id", "home_abbr", "away_abbr", "home_score", "away_score",
|
|
"home_record", "away_record", "start_time_utc"):
|
|
assert key in result
|
|
|
|
def test_team_abbreviations(self, helper):
|
|
event = _make_espn_event(home_abbr="MIA", away_abbr="PHX")
|
|
result = helper.extract_game_details(event)
|
|
assert result["home_abbr"] == "MIA"
|
|
assert result["away_abbr"] == "PHX"
|
|
|
|
def test_scores_as_strings(self, helper):
|
|
event = _make_espn_event(home_score="110", away_score="99")
|
|
result = helper.extract_game_details(event)
|
|
assert result["home_score"] == "110"
|
|
assert result["away_score"] == "99"
|
|
|
|
def test_returns_none_on_empty(self, helper):
|
|
assert helper.extract_game_details({}) is None
|
|
assert helper.extract_game_details(None) is None
|
|
|
|
def test_returns_none_when_no_competitors(self, helper):
|
|
event = _make_espn_event()
|
|
event["competitions"][0]["competitors"] = []
|
|
assert helper.extract_game_details(event) is None
|
|
|
|
def test_date_z_suffix_parsed(self, helper):
|
|
event = _make_espn_event(date_str="2024-06-01T19:30:00Z")
|
|
result = helper.extract_game_details(event)
|
|
assert result["start_time_utc"] is not None
|
|
assert result["start_time_utc"].tzinfo is not None
|
|
|
|
def test_zero_zero_record_suppressed(self, helper):
|
|
event = _make_espn_event(home_record="0-0", away_record="0-0-0")
|
|
result = helper.extract_game_details(event)
|
|
assert result["home_record"] == ""
|
|
assert result["away_record"] == ""
|
|
|
|
def test_basketball_sport_fields(self, helper):
|
|
event = _make_espn_event(period=3)
|
|
result = helper.extract_game_details(event, sport="basketball")
|
|
assert result["period_text"] == "Q3"
|
|
assert "clock" in result
|
|
|
|
def test_basketball_overtime_period(self, helper):
|
|
event = _make_espn_event(period=5)
|
|
result = helper.extract_game_details(event, sport="basketball")
|
|
assert result["period_text"] == "OT1"
|
|
|
|
def test_football_sport_fields(self, helper):
|
|
event = _make_espn_event(period=2)
|
|
result = helper.extract_game_details(event, sport="football")
|
|
assert result["period_text"] == "Q2"
|
|
|
|
def test_hockey_sport_fields_period_1(self, helper):
|
|
event = _make_espn_event(period=1)
|
|
result = helper.extract_game_details(event, sport="hockey")
|
|
assert result["period_text"] == "P1"
|
|
|
|
def test_hockey_sport_fields_ot(self, helper):
|
|
event = _make_espn_event(period=4)
|
|
result = helper.extract_game_details(event, sport="hockey")
|
|
assert result["period_text"] == "OT1"
|
|
|
|
def test_baseball_sport_fields(self, helper):
|
|
event = _make_espn_event(period=7)
|
|
result = helper.extract_game_details(event, sport="baseball")
|
|
assert result["period_text"] == "INN 7"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Filter methods
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestFilterMethods:
|
|
def _make_games(self):
|
|
now = datetime.now(timezone.utc)
|
|
return [
|
|
{"is_live": True, "is_final": False, "is_upcoming": False, "home_abbr": "LAL", "away_abbr": "BOS", "start_time_utc": now},
|
|
{"is_live": False, "is_final": True, "is_upcoming": False, "home_abbr": "MIA", "away_abbr": "PHX", "start_time_utc": now - timedelta(hours=3)},
|
|
{"is_live": False, "is_final": False, "is_upcoming": True, "home_abbr": "DAL", "away_abbr": "CHI", "start_time_utc": now + timedelta(hours=2)},
|
|
]
|
|
|
|
def test_filter_live_games(self, helper):
|
|
games = self._make_games()
|
|
result = helper.filter_live_games(games)
|
|
assert len(result) == 1
|
|
assert result[0]["home_abbr"] == "LAL"
|
|
|
|
def test_filter_final_games(self, helper):
|
|
games = self._make_games()
|
|
result = helper.filter_final_games(games)
|
|
assert len(result) == 1
|
|
assert result[0]["home_abbr"] == "MIA"
|
|
|
|
def test_filter_upcoming_games(self, helper):
|
|
games = self._make_games()
|
|
result = helper.filter_upcoming_games(games)
|
|
assert len(result) == 1
|
|
assert result[0]["home_abbr"] == "DAL"
|
|
|
|
def test_filter_favorite_teams_match(self, helper):
|
|
games = self._make_games()
|
|
result = helper.filter_favorite_teams(games, ["LAL"])
|
|
assert len(result) == 1
|
|
assert result[0]["home_abbr"] == "LAL"
|
|
|
|
def test_filter_favorite_teams_empty_list_returns_all(self, helper):
|
|
games = self._make_games()
|
|
result = helper.filter_favorite_teams(games, [])
|
|
assert len(result) == 3
|
|
|
|
def test_filter_favorite_teams_away_match(self, helper):
|
|
games = self._make_games()
|
|
result = helper.filter_favorite_teams(games, ["BOS"])
|
|
assert len(result) == 1
|
|
|
|
def test_filter_recent_games_within_window(self, helper):
|
|
now = datetime.now(timezone.utc)
|
|
games = [
|
|
{"start_time_utc": now - timedelta(days=2), "is_final": True},
|
|
{"start_time_utc": now - timedelta(days=10), "is_final": True},
|
|
]
|
|
result = helper.filter_recent_games(games, days_back=7)
|
|
assert len(result) == 1
|
|
|
|
def test_filter_recent_games_all_within(self, helper):
|
|
now = datetime.now(timezone.utc)
|
|
games = [
|
|
{"start_time_utc": now - timedelta(days=1)},
|
|
{"start_time_utc": now - timedelta(days=3)},
|
|
]
|
|
result = helper.filter_recent_games(games, days_back=7)
|
|
assert len(result) == 2
|
|
|
|
def test_sort_games_ascending(self, helper):
|
|
now = datetime.now(timezone.utc)
|
|
games = [
|
|
{"start_time_utc": now + timedelta(hours=2), "id": "late"},
|
|
{"start_time_utc": now + timedelta(hours=1), "id": "early"},
|
|
]
|
|
result = helper.sort_games_by_time(games)
|
|
assert result[0]["id"] == "early"
|
|
|
|
def test_sort_games_descending(self, helper):
|
|
now = datetime.now(timezone.utc)
|
|
games = [
|
|
{"start_time_utc": now + timedelta(hours=1), "id": "early"},
|
|
{"start_time_utc": now + timedelta(hours=2), "id": "late"},
|
|
]
|
|
result = helper.sort_games_by_time(games, reverse=True)
|
|
assert result[0]["id"] == "late"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# process_games
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestProcessGames:
|
|
def test_processes_valid_events(self, helper):
|
|
events = [
|
|
_make_espn_event(event_id="1"),
|
|
_make_espn_event(event_id="2"),
|
|
]
|
|
result = helper.process_games(events)
|
|
assert len(result) == 2
|
|
|
|
def test_skips_invalid_events(self, helper):
|
|
events = [
|
|
_make_espn_event(event_id="1"),
|
|
{}, # invalid
|
|
]
|
|
result = helper.process_games(events)
|
|
assert len(result) == 1
|
|
|
|
def test_empty_events(self, helper):
|
|
assert helper.process_games([]) == []
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# get_game_summary
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestGetGameSummary:
|
|
def test_live_summary(self, helper):
|
|
game = {
|
|
"home_abbr": "LAL", "away_abbr": "BOS",
|
|
"home_score": "105", "away_score": "98",
|
|
"status_text": "Q4 2:30",
|
|
"is_live": True, "is_final": False,
|
|
}
|
|
summary = helper.get_game_summary(game)
|
|
assert "BOS" in summary
|
|
assert "LAL" in summary
|
|
assert "98" in summary
|
|
assert "105" in summary
|
|
|
|
def test_final_summary(self, helper):
|
|
game = {
|
|
"home_abbr": "LAL", "away_abbr": "BOS",
|
|
"home_score": "110", "away_score": "102",
|
|
"status_text": "Final",
|
|
"is_live": False, "is_final": True,
|
|
}
|
|
summary = helper.get_game_summary(game)
|
|
assert "Final" in summary
|
|
|
|
def test_upcoming_summary(self, helper):
|
|
game = {
|
|
"home_abbr": "LAL", "away_abbr": "BOS",
|
|
"home_score": "0", "away_score": "0",
|
|
"status_text": "7:30 PM",
|
|
"is_live": False, "is_final": False,
|
|
}
|
|
summary = helper.get_game_summary(game)
|
|
assert "7:30 PM" in summary
|