"""Run actual penalty reconciliation with deterministic browser/physical I/O.""" from __future__ import annotations import json import os from pathlib import Path import shutil import subprocess import pytest from hockey_data.service import HockeyDataService ROOT = Path(__file__).resolve().parents[1] SCENARIOS = tuple( scenario for side in ("home", "away") for scenario in ( f"existing_{side}_two_add_four_running", f"existing_{side}_two_add_four_paused", f"existing_{side}_four_add_two", f"expired_two_continues_four_{side}", ) ) + ("authoritative_missing_plate_is_restored", "strict_four_minute_shorten_then_pause", "strict_four_minute_shorten_then_resume") def strength_fixtures() -> dict: settings = {"strength_regulation_skaters": 5, "strength_min_skaters": 3} result = {} for home in range(3): for away in range(3): penalties = [ {"side": side, "teamPenalty": True, "infraction": {"id": "minor"}, "preset": "2", "durationMs": 120000, "remainingMs": 85000, "running": True, "startedOnce": True, "finished": False} for side, count in (("home", home), ("away", away)) for _ in range(count) ] result[f"{home}:{away}"] = HockeyDataService._strength_payload( settings, stage="regular", current_period="2", timer_state={"penalty_board": {"penalties": penalties}}, language="ru", ) return result @pytest.mark.parametrize("scenario", SCENARIOS) def test_penalty_clock_reconciliation(scenario: str) -> None: node = shutil.which("node") assert node, "Node is required for production-function regressions" source = Path(os.environ.get("BUILD199_RUNTIME_SOURCE", ROOT / "ui_builder/static/app.js")) completed = subprocess.run( [node, str(ROOT / "tests/build199_penalty_clock.cjs"), scenario, str(source)], input=json.dumps({"strengths": strength_fixtures()}, ensure_ascii=False), text=True, capture_output=True, timeout=20, ) assert completed.returncode == 0, completed.stdout + completed.stderr assert f"PASS: {scenario}" in completed.stdout