import json from tests.conftest import make_game, make_playoff_game class TestIndexRoute: def test_returns_200(self, flask_client): response = flask_client.get("/") assert response.status_code == 200 def test_returns_html(self, flask_client): response = flask_client.get("/") assert b"NHL Scoreboard" in response.data class TestScoreboardRoute: def test_returns_200(self, flask_client): response = flask_client.get("/scoreboard") assert response.status_code == 200 def test_returns_json_with_expected_keys(self, flask_client): response = flask_client.get("/scoreboard") data = json.loads(response.data) assert "live_games" in data assert "intermission_games" in data assert "pre_games" in data assert "final_games" in data def test_intermission_games_separated_from_live( self, flask_client, monkeypatch, tmp_path ): import json as _json import app.routes as routes intermission_game = make_game(in_intermission=True) live_game = make_game(home_name="Oilers", away_name="Flames") scoreboard = {"games": [intermission_game, live_game]} f = tmp_path / "scoreboard_data.json" f.write_text(_json.dumps(scoreboard)) monkeypatch.setattr(routes, "SCOREBOARD_DATA_FILE", str(f)) data = _json.loads(flask_client.get("/scoreboard").data) assert len(data["intermission_games"]) == 1 assert data["intermission_games"][0]["Intermission"] is True assert len(data["live_games"]) == 1 assert data["live_games"][0]["Intermission"] is False def test_live_games_have_required_fields(self, flask_client): response = flask_client.get("/scoreboard") data = json.loads(response.data) if data["live_games"]: game = data["live_games"][0] assert "Home Team" in game assert "Away Team" in game assert "Home Score" in game assert "Away Score" in game assert "Game State" in game assert game["Game State"] == "LIVE" def test_missing_file_returns_error(self, flask_client, monkeypatch): import app.routes as routes monkeypatch.setattr(routes, "SCOREBOARD_DATA_FILE", "/nonexistent/path.json") response = flask_client.get("/scoreboard") data = json.loads(response.data) assert "error" in data def test_invalid_json_returns_error(self, flask_client, monkeypatch, tmp_path): import app.routes as routes bad_file = tmp_path / "bad.json" bad_file.write_text("not valid json {{{") monkeypatch.setattr(routes, "SCOREBOARD_DATA_FILE", str(bad_file)) response = flask_client.get("/scoreboard") data = json.loads(response.data) assert "error" in data def test_null_json_returns_error(self, flask_client, monkeypatch, tmp_path): import app.routes as routes null_file = tmp_path / "null.json" null_file.write_text("null") monkeypatch.setattr(routes, "SCOREBOARD_DATA_FILE", str(null_file)) response = flask_client.get("/scoreboard") data = json.loads(response.data) assert "error" in data def test_meta_and_pinned_keys_present(self, flask_client): response = flask_client.get("/scoreboard") data = json.loads(response.data) assert "meta" in data assert "pinned_games" in data assert "playoff_mode" in data["meta"] def test_meta_playoff_mode_off_for_regular_season(self, flask_client): response = flask_client.get("/scoreboard") data = json.loads(response.data) assert data["meta"]["playoff_mode"] is False def test_playoff_day_populates_meta(self, flask_client, monkeypatch, tmp_path): import app.routes as routes playoff_game = make_playoff_game( top_wins=3, bottom_wins=3, round_num=1, series_letter="A", game_state="LIVE", ) scoreboard = {"games": [playoff_game]} f = tmp_path / "scoreboard_data.json" f.write_text(json.dumps(scoreboard)) monkeypatch.setattr(routes, "SCOREBOARD_DATA_FILE", str(f)) data = json.loads(flask_client.get("/scoreboard").data) assert data["meta"]["playoff_mode"] is True assert data["meta"]["round_label"] == "First Round" assert data["meta"]["game7_count"] == 1 assert data["meta"]["series_active"] == 1 def test_game7_goes_to_pinned_bucket_not_live( self, flask_client, monkeypatch, tmp_path ): import app.routes as routes g7 = make_playoff_game( top_wins=3, bottom_wins=3, game_state="LIVE", home_name="Kings", away_name="Oilers", ) regular_live = make_game(home_name="Rangers", away_name="Devils") scoreboard = {"games": [g7, regular_live]} f = tmp_path / "scoreboard_data.json" f.write_text(json.dumps(scoreboard)) monkeypatch.setattr(routes, "SCOREBOARD_DATA_FILE", str(f)) data = json.loads(flask_client.get("/scoreboard").data) pinned_names = [g["Home Team"] for g in data["pinned_games"]] live_names = [g["Home Team"] for g in data["live_games"]] assert "Kings" in pinned_names assert "Kings" not in live_names assert "Rangers" in live_names class TestSeriesDetailRoute: _SAMPLE_PAYLOAD = { "round": 1, "roundLabel": "1st-round", "seriesLetter": "A", "neededToWin": 4, "length": 7, "topSeedTeam": { "id": 10, "name": {"default": "Maple Leafs"}, "abbrev": "TOR", "placeName": {"default": "Toronto"}, "record": "2-1", "seriesWins": 2, "divisionAbbrev": "A", "seed": 1, "logo": "https://example.com/tor.svg", "darkLogo": "https://example.com/tor_dark.svg", "conference": {"abbrev": "E"}, }, "bottomSeedTeam": { "id": 9, "name": {"default": "Senators"}, "abbrev": "OTT", "placeName": {"default": "Ottawa"}, "record": "1-2", "seriesWins": 1, "divisionAbbrev": "A", "seed": 4, "logo": "https://example.com/ott.svg", "darkLogo": "https://example.com/ott_dark.svg", "conference": {"abbrev": "E"}, }, "games": [ { "id": 1, "gameNumber": 1, "ifNecessary": False, "venue": {"default": "Arena"}, "startTimeUTC": "2026-04-18T23:00:00Z", "gameState": "OFF", "periodDescriptor": {"number": 3, "periodType": "REG"}, "awayTeam": {"abbrev": "OTT", "score": 2, "commonName": {"default": "Senators"}}, "homeTeam": {"abbrev": "TOR", "score": 6, "commonName": {"default": "Maple Leafs"}}, "gameOutcome": {"lastPeriodType": "REG"}, }, { "id": 2, "gameNumber": 4, "ifNecessary": False, "venue": {"default": "Arena"}, "startTimeUTC": "2026-04-22T23:00:00Z", "gameState": "FUT", "periodDescriptor": {"number": 1, "periodType": "REG"}, "awayTeam": {"abbrev": "TOR", "commonName": {"default": "Maple Leafs"}}, "homeTeam": {"abbrev": "OTT", "commonName": {"default": "Senators"}}, }, ], } def test_invalid_series_id_404(self, flask_client): response = flask_client.get("/series/garbage") assert response.status_code == 404 def test_valid_format_but_missing_cache_404(self, flask_client, monkeypatch): import app.routes as routes monkeypatch.setattr(routes, "fetch_series", lambda sid: None) response = flask_client.get("/series/2026-A") assert response.status_code == 404 def test_renders_with_cached_payload(self, flask_client, monkeypatch): import app.routes as routes monkeypatch.setattr(routes, "fetch_series", lambda sid: self._SAMPLE_PAYLOAD) response = flask_client.get("/series/2026-A") assert response.status_code == 200 body = response.data.decode("utf-8") assert "Maple Leafs" in body assert "Senators" in body assert "Game 1" in body assert "Game 4" in body def test_letter_out_of_range_404(self, flask_client): response = flask_client.get("/series/2026-Z") assert response.status_code == 404 class TestBracketRoute: _BRACKET = { "bracketLogo": "http://example.com/bracket.png", "series": [ { "seriesLetter": "A", "playoffRound": 1, "topSeedWins": 2, "bottomSeedWins": 1, "topSeedRankAbbrev": "D1", "bottomSeedRankAbbrev": "WC1", "winningTeamId": None, "topSeedTeam": { "id": 10, "abbrev": "TOR", "name": {"default": "Toronto Maple Leafs"}, "commonName": {"default": "Maple Leafs"}, "darkLogo": "http://example.com/TOR.svg", }, "bottomSeedTeam": { "id": 9, "abbrev": "OTT", "name": {"default": "Ottawa Senators"}, "commonName": {"default": "Senators"}, "darkLogo": "http://example.com/OTT.svg", }, } ], } def test_bracket_renders_with_cache(self, flask_client, monkeypatch): import app.routes as routes monkeypatch.setattr(routes, "get_bracket", lambda y: (self._BRACKET, 123)) monkeypatch.setattr( routes, "refresh_bracket", lambda y=None: (_ for _ in ()).throw(AssertionError("should not refetch")), ) response = flask_client.get("/bracket") assert response.status_code == 200 body = response.data.decode("utf-8") assert "Stanley Cup Playoffs" in body assert "TOR" in body assert "OTT" in body def test_bracket_falls_back_to_fresh_fetch_when_cache_empty( self, flask_client, monkeypatch ): import app.routes as routes monkeypatch.setattr(routes, "get_bracket", lambda y: (None, None)) called = {"n": 0} def fake_refresh(year=None): called["n"] += 1 return self._BRACKET monkeypatch.setattr(routes, "refresh_bracket", fake_refresh) response = flask_client.get("/bracket") assert response.status_code == 200 assert called["n"] == 1 def test_bracket_returns_404_when_no_data(self, flask_client, monkeypatch): import app.routes as routes monkeypatch.setattr(routes, "get_bracket", lambda y: (None, None)) monkeypatch.setattr(routes, "refresh_bracket", lambda year=None: None) response = flask_client.get("/bracket") assert response.status_code == 404