from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from hockey_data.client import Stat2TVNotFoundError, Stat2TVResponse
from hockey_data.config import HockeySettingsStore
from hockey_data.models import Tournament
from hockey_data.service import HockeyDataService
from support import LocalTestDatabase
from hockey_data.tournament_statistics_parser import parse_tournament_statistics_xml
POWERPLAY_XML = """
"""
RANK_XML = """
"""
RANK_NESTED_XML = """
Сидоров Алексей
Alexei Sidorov
Команда В
Team C
"""
EMPTY_POWERPLAY_XML = """
"""
RANK_WRAPPED_STATS_XML = """
7
6
94.2
"""
class TournamentStatisticsParserTests(unittest.TestCase):
def test_parses_powerplay_team_rows(self) -> None:
result = parse_tournament_statistics_xml(
POWERPLAY_XML, resource_type="powerplay", language="ru"
)
self.assertTrue(result["available"])
self.assertEqual(result["tournament_id"], "1370")
self.assertEqual(len(result["sections"]), 1)
first = result["sections"][0]["rows"][0]
self.assertEqual(first["name"], "Команда А")
self.assertEqual(first["rank"], "1")
self.assertEqual(first["values"]["ppg"], "8")
self.assertEqual(
[column["key"] for column in result["sections"][0]["columns"]],
["gp", "ppg", "ppo", "pp_pct"],
)
def test_parses_rank_categories_and_english_names(self) -> None:
result = parse_tournament_statistics_xml(
RANK_XML, resource_type="rank", language="en"
)
self.assertTrue(result["available"])
self.assertEqual([section["id"] for section in result["sections"]], ["points", "goals"])
self.assertEqual(result["sections"][0]["label"], "Points leaders")
self.assertEqual(result["sections"][0]["rows"][0]["name"], "Ivan Ivanov")
self.assertEqual(result["sections"][0]["rows"][0]["team"], "Team A")
def test_parses_nested_stat_fields_without_technical_duplicate_rows(self) -> None:
result = parse_tournament_statistics_xml(
RANK_NESTED_XML, resource_type="rank", language="ru"
)
self.assertTrue(result["available"])
self.assertEqual(len(result["sections"]), 1)
self.assertEqual(len(result["sections"][0]["rows"]), 1)
row = result["sections"][0]["rows"][0]
self.assertEqual(row["name"], "Сидоров Алексей")
self.assertEqual(row["values"], {"gp": "5", "a": "9", "pts": "10"})
self.assertEqual(row["source_tag"], "Player")
def test_parses_stats_wrapped_in_container(self) -> None:
result = parse_tournament_statistics_xml(
RANK_WRAPPED_STATS_XML, resource_type="rank", language="en"
)
self.assertTrue(result["available"])
self.assertEqual(result["tournament_id"], "1373")
row = result["sections"][0]["rows"][0]
self.assertEqual(row["name"], "Maxim Orlov")
self.assertEqual(row["values"]["gp"], "7")
self.assertEqual(row["values"]["wins"], "6")
self.assertEqual(row["values"]["save_pct"], "94.2")
def test_empty_valid_xml_is_marked_unavailable(self) -> None:
result = parse_tournament_statistics_xml(
EMPTY_POWERPLAY_XML, resource_type="powerplay", language="ru"
)
self.assertFalse(result["available"])
self.assertEqual(result["sections"], [])
class TournamentStatisticsCachingTests(unittest.IsolatedAsyncioTestCase):
async def test_mixed_optional_resources_are_cached_without_repeated_requests(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
base_dir = Path(temp_dir)
database = LocalTestDatabase(base_dir / "test.sqlite3")
database.create_all()
settings = HockeySettingsStore(base_dir / "settings")
service = HockeyDataService(
base_dir=base_dir,
database=database,
settings=settings,
)
with database.session() as session:
session.add(Tournament(external_id="1370"))
calls: list[str] = []
async def fake_fetch(endpoint: str) -> Stat2TVResponse:
calls.append(endpoint)
if "powerplay-1370.xml" in endpoint:
return Stat2TVResponse(
content=POWERPLAY_XML.encode("utf-8"),
status_code=200,
content_type="application/xml",
auth_mode="test",
url=f"https://example.test/{endpoint}",
)
raise Stat2TVNotFoundError(
f"https://example.test/{endpoint}", status_code=404
)
service.client.fetch_xml = fake_fetch # type: ignore[method-assign]
first = await service.sync_tournament_statistics("1370")
second = await service.sync_tournament_statistics("1370")
self.assertTrue(first["statistics"]["powerplay"]["available"])
self.assertFalse(first["statistics"]["rank"]["available"])
self.assertEqual(first["statistics"]["available_tabs"], ["powerplay"])
self.assertEqual(len(calls), 2)
self.assertTrue(second["resources"]["powerplay"]["skipped"])
self.assertTrue(second["resources"]["rank"]["skipped"])
self.assertEqual(len(calls), 2)
database.engine.dispose()
async def test_valid_empty_xml_uses_negative_cache(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
base_dir = Path(temp_dir)
database = LocalTestDatabase(base_dir / "test.sqlite3")
database.create_all()
settings = HockeySettingsStore(base_dir / "settings")
service = HockeyDataService(
base_dir=base_dir,
database=database,
settings=settings,
)
with database.session() as session:
session.add(Tournament(external_id="1372"))
calls: list[str] = []
async def fake_fetch(endpoint: str) -> Stat2TVResponse:
calls.append(endpoint)
return Stat2TVResponse(
content=EMPTY_POWERPLAY_XML.encode("utf-8"),
status_code=200,
content_type="application/xml",
auth_mode="test",
url=f"https://example.test/{endpoint}",
)
service.client.fetch_xml = fake_fetch # type: ignore[method-assign]
await service.sync_tournament_statistics("1372")
second = await service.sync_tournament_statistics("1372")
self.assertEqual(len(calls), 2) # powerplay + rank only on first sync
self.assertTrue(second["resources"]["powerplay"]["skipped"])
self.assertTrue(second["resources"]["rank"]["skipped"])
database.engine.dispose()
if __name__ == "__main__":
unittest.main()