Files
NHL-Scoreboard/tests/test_playoff_series.py
T
josh b5ab318e05
CI / Lint (push) Successful in 8s
CI / Test (push) Successful in 11s
CI / Build & Push (push) Successful in 19s
fix: drop game_number by one for finished playoff cards
Scoreboard payloads don't include gameNumber, so the previous fix fell straight through to series_state['game_number'], which is hi+lo+1 — the *next* game. Once a game goes FINAL the win for this game is already banked in seriesStatus, so the card's own number is hi+lo. For the just-played Game 1 of Kings/Avalanche (series now 1-0), this brings the header back to "Game 1 of 7" instead of "Game 2 of 7".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 18:47:50 -04:00

288 lines
9.9 KiB
Python

import pytest
from app.playoff import (
is_pinned,
is_playoff_game,
is_playoff_ot,
ot_label,
series_badges,
series_blurb,
series_state,
series_summary,
today_meta,
)
from tests.conftest import make_game, make_playoff_game
class TestSeriesState:
def test_empty_returns_defaults(self):
state = series_state({})
assert state["is_opener"] is True
assert state["game_number"] == 1
assert state["round"] == 1
assert state["leader"] is None
@pytest.mark.parametrize(
"top,bot,expected_game",
[(0, 0, 1), (1, 0, 2), (1, 1, 3), (2, 1, 4), (2, 2, 5), (3, 2, 6), (3, 3, 7)],
)
def test_game_number_computation(self, top, bot, expected_game):
state = series_state({"round": 1, "topSeedWins": top, "bottomSeedWins": bot})
assert state["game_number"] == expected_game
def test_game7_predicate(self):
state = series_state({"round": 1, "topSeedWins": 3, "bottomSeedWins": 3})
assert state["is_game7"] is True
assert state["is_clincher"] is False
assert state["is_pivotal"] is False
def test_clincher_predicate(self):
state = series_state({"round": 1, "topSeedWins": 3, "bottomSeedWins": 1})
assert state["is_clincher"] is True
assert state["is_elimination"] is True
assert state["is_game7"] is False
def test_pivotal_predicate(self):
state = series_state({"round": 2, "topSeedWins": 2, "bottomSeedWins": 2})
assert state["is_pivotal"] is True
assert state["is_game7"] is False
assert state["is_clincher"] is False
def test_opener_predicate(self):
state = series_state({"round": 1, "topSeedWins": 0, "bottomSeedWins": 0})
assert state["is_opener"] is True
def test_leader_top(self):
state = series_state({"round": 1, "topSeedWins": 2, "bottomSeedWins": 1})
assert state["leader"] == "top"
assert state["hi"] == 2 and state["lo"] == 1
def test_leader_bottom(self):
state = series_state({"round": 1, "topSeedWins": 1, "bottomSeedWins": 2})
assert state["leader"] == "bottom"
def test_no_leader_when_tied(self):
state = series_state({"round": 1, "topSeedWins": 1, "bottomSeedWins": 1})
assert state["leader"] is None
class TestSeriesBlurb:
def test_opener_blurb(self):
game = make_playoff_game(top_wins=0, bottom_wins=0)
assert series_blurb(game) == "Series opener"
def test_game7_blurb(self):
game = make_playoff_game(top_wins=3, bottom_wins=3)
assert series_blurb(game) == "Win-or-go-home \u2014 Game 7."
def test_clincher_blurb_names_leader(self):
game = make_playoff_game(
top_wins=3,
bottom_wins=1,
top_abbrev="TOR",
bottom_abbrev="BOS",
top_is_home=True,
)
blurb = series_blurb(game)
assert "Top Seeds" in blurb
assert "close it out" in blurb
assert "Game 5" in blurb
def test_pivotal_blurb(self):
game = make_playoff_game(top_wins=2, bottom_wins=2)
assert "pivotal" in series_blurb(game).lower()
assert "Game 5" in series_blurb(game)
def test_leader_trailer_blurb(self):
game = make_playoff_game(top_wins=2, bottom_wins=1)
blurb = series_blurb(game)
assert "lead" in blurb
assert "2\u20111" in blurb
def test_tied_mid_series_blurb(self):
game = make_playoff_game(top_wins=1, bottom_wins=1)
blurb = series_blurb(game)
assert "1" in blurb
assert "Game 3" in blurb
class TestSeriesBadges:
def test_round_1_always_first(self):
game = make_playoff_game(round_num=1, top_wins=1, bottom_wins=0)
assert series_badges(game)[0] == "R1"
def test_cup_final_label(self):
game = make_playoff_game(round_num=4, top_wins=0, bottom_wins=0)
assert series_badges(game)[0] == "CUP FINAL"
def test_conf_final_label(self):
game = make_playoff_game(round_num=3, top_wins=0, bottom_wins=0)
assert series_badges(game)[0] == "CONF FINAL"
def test_game7_badge(self):
game = make_playoff_game(top_wins=3, bottom_wins=3)
assert "GAME 7" in series_badges(game)
def test_clincher_badge(self):
game = make_playoff_game(top_wins=3, bottom_wins=1)
assert "CLINCHER" in series_badges(game)
def test_pivotal_badge(self):
game = make_playoff_game(top_wins=2, bottom_wins=2)
assert "PIVOTAL" in series_badges(game)
def test_opener_has_no_stake_badge(self):
badges = series_badges(make_playoff_game(top_wins=0, bottom_wins=0))
assert badges == ["R1"]
class TestSeriesSummary:
def test_opener_summary(self):
game = make_playoff_game(top_wins=0, bottom_wins=0, round_num=1)
assert series_summary(game) == "Game 1 of 7"
def test_leader_summary(self):
game = make_playoff_game(top_wins=2, bottom_wins=1)
assert series_summary(game) == "Game 4 of 7"
def test_tied_mid_series_summary(self):
game = make_playoff_game(top_wins=1, bottom_wins=1)
assert series_summary(game) == "Game 3 of 7"
def test_finished_game_uses_pre_advance_number(self):
# Scoreboard payloads don't carry gameNumber. Once a game goes FINAL,
# seriesStatus already includes this game's result, so the card's game
# number is hi+lo, not hi+lo+1.
game = make_playoff_game(top_wins=1, bottom_wins=0, game_state="FINAL")
assert series_summary(game) == "Game 1 of 7"
def test_finished_game_honors_explicit_game_number(self):
game = make_playoff_game(
top_wins=2, bottom_wins=0, game_state="FINAL", game_number=2
)
assert series_summary(game) == "Game 2 of 7"
class TestIsPinned:
def test_game7_live_is_pinned(self):
game = make_playoff_game(top_wins=3, bottom_wins=3, game_state="LIVE")
assert is_pinned(game) is True
def test_game7_pre_is_pinned(self):
game = make_playoff_game(top_wins=3, bottom_wins=3, game_state="FUT")
assert is_pinned(game) is True
def test_game7_final_not_pinned(self):
game = make_playoff_game(top_wins=3, bottom_wins=3, game_state="OFF")
assert is_pinned(game) is False
def test_non_game7_not_pinned(self):
game = make_playoff_game(top_wins=2, bottom_wins=1)
assert is_pinned(game) is False
def test_regular_season_not_pinned(self):
game = make_game() # game_type=2, no series
assert is_pinned(game) is False
class TestIsPlayoffOt:
def test_playoff_period_4_live(self):
game = make_playoff_game(period=4, game_state="LIVE")
assert is_playoff_ot(game) is True
def test_playoff_period_5_live(self):
game = make_playoff_game(period=5, game_state="LIVE")
assert is_playoff_ot(game) is True
def test_playoff_period_3_not_ot(self):
game = make_playoff_game(period=3, game_state="LIVE")
assert is_playoff_ot(game) is False
def test_regular_season_ot_not_playoff_ot(self):
game = make_game(period=4, game_state="LIVE", game_type=2)
assert is_playoff_ot(game) is False
def test_crit_state_counts_as_live(self):
game = make_playoff_game(period=4, game_state="CRIT")
assert is_playoff_ot(game) is True
def test_final_state_not_playoff_ot(self):
game = make_playoff_game(period=4, game_state="OFF")
assert is_playoff_ot(game) is False
class TestOtLabel:
def test_period_4_is_ot(self):
assert ot_label(4) == "OT"
def test_period_5_is_2ot(self):
assert ot_label(5) == "2OT"
def test_period_6_is_3ot(self):
assert ot_label(6) == "3OT"
def test_pre_ot_returns_empty(self):
assert ot_label(3) == ""
assert ot_label(0) == ""
class TestIsPlayoffGame:
def test_playoff_raw_shape(self):
assert is_playoff_game(make_playoff_game()) is True
def test_regular_raw_shape(self):
assert is_playoff_game(make_game(game_type=2)) is False
def test_parsed_shape(self):
# parse_games produces {"Game Type": 3} — is_playoff_game should handle both
assert is_playoff_game({"Game Type": 3}) is True
assert is_playoff_game({"Game Type": 2}) is False
class TestTodayMeta:
def test_no_playoff_games_off_mode(self):
meta = today_meta([make_game(game_type=2)])
assert meta["playoff_mode"] is False
assert meta["round_label"] is None
def test_playoff_games_on_mode(self):
games = [
make_playoff_game(series_letter="A"),
make_playoff_game(series_letter="B"),
]
meta = today_meta(games)
assert meta["playoff_mode"] is True
assert meta["series_active"] == 2
assert meta["round_label"] == "First Round"
def test_counts_game7(self):
games = [
make_playoff_game(top_wins=3, bottom_wins=3, series_letter="A"),
make_playoff_game(top_wins=1, bottom_wins=0, series_letter="B"),
]
meta = today_meta(games)
assert meta["game7_count"] == 1
assert meta["elimination_count"] == 0
def test_counts_elimination_games(self):
games = [
make_playoff_game(top_wins=3, bottom_wins=1, series_letter="A"),
make_playoff_game(top_wins=3, bottom_wins=2, series_letter="B"),
]
meta = today_meta(games)
assert meta["elimination_count"] == 2
assert meta["game7_count"] == 0
def test_round_label_reflects_highest_active_round(self):
games = [
make_playoff_game(round_num=1, series_letter="A"),
make_playoff_game(round_num=2, series_letter="I"),
]
meta = today_meta(games)
assert meta["round_label"] == "Second Round"
def test_cup_final_label(self):
games = [make_playoff_game(round_num=4, series_letter="P")]
meta = today_meta(games)
assert meta["round_label"] == "Stanley Cup Final"