Files
hockey_new/tests/test_build96_penalty_strength_transition_state_machine.py

152 lines
6.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from pathlib import Path
from hockey_data.service import HockeyDataService
ROOT = Path(__file__).resolve().parents[1]
APP_JS = (ROOT / "ui_builder/static/app.js").read_text(encoding="utf-8")
APP = (ROOT / "app.py").read_text(encoding="utf-8")
def _penalty(side: str, remaining: int, *, started: bool) -> dict:
return {
"side": side,
"infraction": {"id": "minor"},
"preset": "2m",
"durationMs": 120000,
"remainingMs": remaining,
"running": False,
"startedOnce": started,
"finished": False,
}
def _strength(*penalties: dict) -> dict:
settings = {
"strength_regulation_skaters": 5,
"strength_min_skaters": 3,
"strength_state_labels": {
"regulation": {
"5x4": {"ru": "БОЛ"},
"4x4": {"ru": "4x4"},
"4x3": {"ru": "4x3"},
}
},
}
return HockeyDataService._strength_payload(
settings,
stage="regular",
current_period="2",
timer_state={"penalty_board": {"penalties": list(penalties)}},
language="ru",
)
def test_prepared_penalty_does_not_change_strength_until_explicit_start():
prepared = _strength(_penalty("home", 120000, started=False))
assert prepared["strength_label"] == "5×5"
assert prepared["advantage_side"] == ""
started = _strength(_penalty("home", 120000, started=True))
assert started["strength_label"] == "4×5"
assert started["advantage_side"] == "away"
assert started["state_label"] == "БОЛ"
def test_complex_transition_strength_sequence_matches_requested_flow():
# Initial HOME penalty: AWAY/right has the advantage.
p1 = _penalty("home", 80000, started=True)
value = _strength(p1)
assert value["strength_label"] == "4×5"
assert value["advantage_side"] == "away"
# Coincidental pair is added while the first penalty is still running.
p2 = _penalty("home", 120000, started=True)
p3 = _penalty("away", 120000, started=True)
value = _strength(p1, p2, p3)
assert value["strength_label"] == "3×4"
assert value["advantage_side"] == "away"
assert value["state_label"] == "4x3"
# The first penalty expires: the pair remains, so strength is equal 4x4.
value = _strength(
_penalty("home", 40000, started=True),
_penalty("away", 40000, started=True),
)
assert value["strength_label"] == "4×4"
assert value["advantage_side"] == ""
assert value["state_label"] == "4x4"
# AWAY/right takes another penalty: HOME/left now has a 4x3 advantage.
value = _strength(
_penalty("home", 30000, started=True),
_penalty("away", 30000, started=True),
_penalty("away", 120000, started=True),
)
assert value["strength_label"] == "4×3"
assert value["advantage_side"] == "home"
assert value["state_label"] == "4x3"
# Coincidental pair expires, leaving ordinary HOME power play.
value = _strength(_penalty("away", 90000, started=True))
assert value["strength_label"] == "5×4"
assert value["advantage_side"] == "home"
assert value["state_label"] == "БОЛ"
def test_runtime_holds_single_plate_through_temporary_equal_strength():
start = APP_JS.index("function penaltyDisplayEntriesByTargetSide")
end = APP_JS.index("function hockeyPenaltySideMappingDetail", start)
block = APP_JS[start:end]
assert "canHoldEqualStrength" in block
assert "state.hockeyPenaltyAdvantageCycle.hadAdvantage" in block
assert "lastAdvantageSide" in block
assert "const canHoldEqualStrength = Boolean(state.hockeyPenaltyAdvantageCycle.hadAdvantage)" in block
assert 'home: rememberedSide === "home" && transitionEntry ? [transitionEntry] : []' in block
assert 'away: rememberedSide === "away" && transitionEntry ? [transitionEntry] : []' in block
assert 'home: advantageSide === "home" && transitionEntry ? [transitionEntry] : []' in block
assert 'away: advantageSide === "away" && transitionEntry ? [transitionEntry] : []' in block
assert "holdingEqualStrength" in block
# The countdown source remains the globally soonest active penalty.
assert "const transitionEntry = all[0] || null;" in block
def test_pure_equal_strength_without_previous_advantage_still_has_no_plate():
start = APP_JS.index("function penaltyDisplayEntriesByTargetSide")
end = APP_JS.index("function hockeyPenaltySideMappingDetail", start)
block = APP_JS[start:end]
assert "if (!canHoldEqualStrength)" in block
assert "return { home: [], away: [], routedToAdvantage: false" in block
def test_advantage_cycle_is_saved_and_restored_for_reload_during_4x4():
snapshot = APP_JS.split("function hockeyGameTimerSnapshot", 1)[1].split("function hockeyApplySavedTimers", 1)[0]
assert "advantage_cycle" in snapshot
assert "had_advantage" in snapshot
assert "last_advantage_side" in snapshot
restore = APP_JS.split("function hockeyApplySavedTimers", 1)[1].split("async function hockeyPersistGameTimers", 1)[0]
assert "restoredPenaltyAdvantageCycle" in restore
assert "savedCycle.last_advantage_side" in restore
normalized = HockeyDataService._normalise_game_timer_state({
"penalty_board": {
"penalties": [],
"history": [],
"advantage_cycle": {"had_advantage": True, "last_advantage_side": "away"},
}
})
assert normalized["penalty_board"]["advantage_cycle"] == {
"had_advantage": True,
"last_advantage_side": "away",
}
def test_full_strength_waits_for_last_started_penalty_not_prepared_draft():
control = APP_JS.split("function controlHockeyPenalty", 1)[1].split("function formatHockeyPenaltyTime", 1)[0]
assert "hockeyPenaltyHasStarted(item)" in control
ticker = APP_JS.split("function updateHockeyPenaltyBoards", 1)[1].split("function registerHockeyBoardNode", 1)[0]
assert "remainingTotal = board.penalties.filter((item) => !item.finished && hockeyEventReady(item) && hockeyPenaltyHasStarted(item)).length" in ticker
def test_build96_runtime_version():
assert 'BUILD_VERSION = "2026.08.20.17"' in APP