F1-Paddock-Oracle / tests /unit /test_commentary_tab.py
NiketKakkar's picture
sync all project files
97e14f1
Raw
History Blame Contribute Delete
6.94 kB
"""Unit tests for the Commentary tab handler (Issue #10)."""
import sys
import types
import tempfile
from pathlib import Path
from unittest.mock import patch, MagicMock
import pandas as pd
import numpy as np
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
# ---------------------------------------------------------------------------
# Helpers / fixtures
# ---------------------------------------------------------------------------
@pytest.fixture()
def lap_df():
rows = []
for lap in range(15, 26):
for driver, pos, team, gap in [
("VER", 1, "Red Bull Racing", 0.0),
("NOR", 2, "McLaren", 4.5),
]:
rows.append({
"lap_number": lap,
"driver_code": driver,
"team": team,
"position": pos,
"gap_to_leader_s": gap,
"compound": "MEDIUM",
"tyre_life": lap - 10,
"lap_time_s": 90.1,
"sc_active": False,
})
return pd.DataFrame(rows)
@pytest.fixture()
def race():
return {"season": 2024, "round": 21, "name": "Brazilian GP", "circuit": "Interlagos", "modes": ["commentary"]}
# ---------------------------------------------------------------------------
# _top_two_drivers
# ---------------------------------------------------------------------------
class TestTopTwoDrivers:
def test_returns_p1_p2_at_pivot_lap(self, lap_df):
with patch("app._CACHE_DIR") as mock_cache, \
patch("app._race_key", return_value="2024_Brazilian_GP"), \
patch("pandas.read_parquet", return_value=lap_df):
from app import _top_two_drivers
driver_a, driver_b, team_name = _top_two_drivers(2024, 21, 20)
assert driver_a == "VER"
assert driver_b == "NOR"
assert team_name == "Red Bull Racing"
def test_falls_back_to_last_lap_when_pivot_missing(self, lap_df):
# pivot_lap=99 doesn't exist — should fall back to lap 25
with patch("app._race_key", return_value="2024_Brazilian_GP"), \
patch("pandas.read_parquet", return_value=lap_df):
from app import _top_two_drivers
driver_a, driver_b, team_name = _top_two_drivers(2024, 21, 99)
assert driver_a == "VER"
assert driver_b == "NOR"
# ---------------------------------------------------------------------------
# _generate_commentary — mocked Modal call
# ---------------------------------------------------------------------------
class TestGenerateCommentary:
def _make_wav_bytes(self):
"""Minimal valid 16-bit WAV bytes (44-byte header + 2 bytes of silence)."""
import struct
data = b"\x00\x00"
header = struct.pack(
"<4sI4s4sIHHIIHH4sI",
b"RIFF", 36 + len(data), b"WAVE",
b"fmt ", 16, 1, 1, 24000, 48000, 2, 16,
b"data", len(data),
)
return header + data
def test_broadcast_returns_audio_path_and_text(self, race, lap_df):
audio_bytes = self._make_wav_bytes()
modal_result = {"text": "Verstappen extends his lead.", "audio_wav": audio_bytes}
with patch("app._top_two_drivers", return_value=("VER", "NOR", "Red Bull Racing")), \
patch("app.get_lap_window", return_value=lap_df), \
patch("app.build_commentary_prompt", return_value="[prompt]"), \
patch("app.call_generate_commentary", return_value=modal_result):
from app import _generate_commentary
audio_path, text = _generate_commentary(race, 20, "Broadcast")
assert text == "Verstappen extends his lead."
assert audio_path is not None
assert audio_path.endswith(".wav")
def test_radio_passes_mode_radio_to_prompt_builder(self, race, lap_df):
modal_result = {"text": "Box this lap.", "audio_wav": b""}
with patch("app._top_two_drivers", return_value=("VER", "NOR", "Red Bull Racing")), \
patch("app.get_lap_window", return_value=lap_df), \
patch("app.build_commentary_prompt", return_value="[prompt]") as mock_build, \
patch("app.call_generate_commentary", return_value=modal_result):
from app import _generate_commentary
_generate_commentary(race, 20, "Radio")
mock_build.assert_called_once_with(lap_df, "Red Bull Racing", "radio")
def test_broadcast_passes_mode_broadcast_to_prompt_builder(self, race, lap_df):
modal_result = {"text": "Hamilton closes in.", "audio_wav": b""}
with patch("app._top_two_drivers", return_value=("VER", "HAM", "Red Bull Racing")), \
patch("app.get_lap_window", return_value=lap_df), \
patch("app.build_commentary_prompt", return_value="[prompt]") as mock_build, \
patch("app.call_generate_commentary", return_value=modal_result):
from app import _generate_commentary
_generate_commentary(race, 20, "Broadcast")
mock_build.assert_called_once_with(lap_df, "Red Bull Racing", "broadcast")
def test_no_audio_bytes_returns_none_path(self, race, lap_df):
modal_result = {"text": "VER leads.", "audio_wav": b""}
with patch("app._top_two_drivers", return_value=("VER", "NOR", "Red Bull Racing")), \
patch("app.get_lap_window", return_value=lap_df), \
patch("app.build_commentary_prompt", return_value="[prompt]"), \
patch("app.call_generate_commentary", return_value=modal_result):
from app import _generate_commentary
audio_path, text = _generate_commentary(race, 20, "Broadcast")
assert audio_path is None
assert text == "VER leads."
# ---------------------------------------------------------------------------
# Two-tier loading copy
# ---------------------------------------------------------------------------
class TestLoadingMessages:
def test_first_call_returns_pit_wall_message(self):
from modal_backend.client import get_commentary_loading_message
import modal_backend.client as client_mod
original = client_mod._commentary_call_count
try:
client_mod._commentary_call_count = 0
msg = get_commentary_loading_message()
assert "pit wall" in msg.lower()
finally:
client_mod._commentary_call_count = original
def test_subsequent_call_returns_shorter_message(self):
from modal_backend.client import get_commentary_loading_message
import modal_backend.client as client_mod
original = client_mod._commentary_call_count
try:
client_mod._commentary_call_count = 1
msg = get_commentary_loading_message()
assert msg != "Connecting to the pit wall… (~20s first call)"
finally:
client_mod._commentary_call_count = original