fix: anchor Day N to each round's first game instead of lazy first sighting
The banner read "Day 1 of ~60" on day 2 of the playoffs because the old anchor recorded whatever date we first polled a playoff game as Day 1. Now round start dates come from /v1/schedule/playoff-series, so Day N is authoritative and resets at each round boundary. Drops the noisy "of ~60" denominator. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+3
-5
@@ -173,15 +173,15 @@ def ot_label(period):
|
|||||||
return "OT" if n == 1 else f"{n}OT"
|
return "OT" if n == 1 else f"{n}OT"
|
||||||
|
|
||||||
|
|
||||||
def today_meta(raw_games, now=None, day_n=None, day_total=None):
|
def today_meta(raw_games, now=None, day_n=None):
|
||||||
"""Build the banner payload from the raw NHL games list.
|
"""Build the banner payload from the raw NHL games list.
|
||||||
|
|
||||||
`raw_games` is the list inside the NHL score response (each with gameType,
|
`raw_games` is the list inside the NHL score response (each with gameType,
|
||||||
seriesStatus, etc.) — NOT the parsed game dicts. This keeps the dependency
|
seriesStatus, etc.) — NOT the parsed game dicts. This keeps the dependency
|
||||||
one-way: playoff.py doesn't need to know parse_games' field names.
|
one-way: playoff.py doesn't need to know parse_games' field names.
|
||||||
|
|
||||||
`day_n` / `day_total` are injected by the caller (from the playoff_cache
|
`day_n` is injected by the caller (from the playoff_cache module) scoped to
|
||||||
module) to keep this function pure and test-friendly.
|
the max observed round so the banner resets at each round boundary.
|
||||||
"""
|
"""
|
||||||
playoff_games = [g for g in raw_games if g.get("gameType") == 3]
|
playoff_games = [g for g in raw_games if g.get("gameType") == 3]
|
||||||
playoff_mode = len(playoff_games) > 0
|
playoff_mode = len(playoff_games) > 0
|
||||||
@@ -191,7 +191,6 @@ def today_meta(raw_games, now=None, day_n=None, day_total=None):
|
|||||||
"playoff_mode": False,
|
"playoff_mode": False,
|
||||||
"round_label": None,
|
"round_label": None,
|
||||||
"day_n": None,
|
"day_n": None,
|
||||||
"day_total": None,
|
|
||||||
"series_active": 0,
|
"series_active": 0,
|
||||||
"elimination_count": 0,
|
"elimination_count": 0,
|
||||||
"game7_count": 0,
|
"game7_count": 0,
|
||||||
@@ -218,7 +217,6 @@ def today_meta(raw_games, now=None, day_n=None, day_total=None):
|
|||||||
"playoff_mode": True,
|
"playoff_mode": True,
|
||||||
"round_label": ROUND_LABELS.get(max_round, f"Round {max_round}"),
|
"round_label": ROUND_LABELS.get(max_round, f"Round {max_round}"),
|
||||||
"day_n": day_n,
|
"day_n": day_n,
|
||||||
"day_total": day_total,
|
|
||||||
"series_active": len(series_letters) if series_letters else len(playoff_games),
|
"series_active": len(series_letters) if series_letters else len(playoff_games),
|
||||||
"elimination_count": elim,
|
"elimination_count": elim,
|
||||||
"game7_count": g7,
|
"game7_count": g7,
|
||||||
|
|||||||
+68
-45
@@ -93,7 +93,6 @@ def refresh_bracket(year=None):
|
|||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
_put(bracket_key(year), data)
|
_put(bracket_key(year), data)
|
||||||
_maybe_record_start_date(data)
|
|
||||||
return data
|
return data
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
logger.warning("Failed to refresh playoff bracket for %s: %s", year, e)
|
logger.warning("Failed to refresh playoff bracket for %s: %s", year, e)
|
||||||
@@ -159,65 +158,89 @@ def fetch_series(series_id):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
# ── Playoff start date (drives the "Day N" banner) ─────────────────
|
# ── Per-round start dates (drive the "Day N" banner) ──────────────
|
||||||
|
|
||||||
META_KEY = "meta:first_playoff_date"
|
ROUND_DATES_KEY = "meta:round_start_dates"
|
||||||
|
|
||||||
|
|
||||||
def _maybe_record_start_date(bracket_payload):
|
def refresh_round_start_dates(year=None):
|
||||||
"""Store the earliest scheduled round-1 series date as Day 1, once.
|
"""Walk the cached bracket + per-series schedules; upsert per-round start dates.
|
||||||
|
|
||||||
The bracket endpoint doesn't include individual games — only the series
|
For each series in the cached bracket, fetches that series' schedule
|
||||||
list. So we record the current day as Day 1 on the first bracket fetch
|
(honoring the TTL cache) and computes the earliest Eastern game date
|
||||||
where any series has nonzero wins OR is otherwise underway. For a perfect
|
within the series. Aggregates to `min(startDate)` per playoffRound and
|
||||||
Day-1 anchor we'd need the per-series schedule; the scoreboard-driven
|
merges into the `meta:round_start_dates` cache entry.
|
||||||
fallback (see record_start_date_if_missing) handles that case.
|
|
||||||
|
Returns the full merged mapping {round_num_str: ISO date} or None if the
|
||||||
|
bracket isn't cached yet.
|
||||||
"""
|
"""
|
||||||
# No-op for now; the scoreboard-driven path is authoritative.
|
if year is None:
|
||||||
return None
|
year = datetime.now(EASTERN).year
|
||||||
|
|
||||||
|
bracket, _ = get_bracket(year)
|
||||||
def record_start_date_if_missing(scoreboard_games, now=None):
|
if bracket is None:
|
||||||
"""Called from routes on every /scoreboard hit.
|
|
||||||
|
|
||||||
If we haven't seen a playoff game yet and the current scoreboard contains
|
|
||||||
one, record today (Eastern) as Day 1. Idempotent after first write.
|
|
||||||
"""
|
|
||||||
existing, _ = _get(META_KEY)
|
|
||||||
if existing and existing.get("first_date"):
|
|
||||||
return existing["first_date"]
|
|
||||||
|
|
||||||
has_playoff = any(g.get("gameType") == 3 for g in scoreboard_games)
|
|
||||||
if not has_playoff:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
now = now or datetime.now(EASTERN)
|
existing, _ = _get(ROUND_DATES_KEY)
|
||||||
today = now.date().isoformat()
|
merged = dict(existing) if existing else {}
|
||||||
_put(
|
|
||||||
META_KEY, {"first_date": today, "recorded_at_utc": now.astimezone().isoformat()}
|
observed = {}
|
||||||
)
|
for series in bracket.get("series", []) or []:
|
||||||
return today
|
letter = series.get("seriesLetter")
|
||||||
|
round_num = series.get("playoffRound")
|
||||||
|
if not letter or not round_num:
|
||||||
|
continue
|
||||||
|
payload = fetch_series(f"{year}-{letter}")
|
||||||
|
if not payload:
|
||||||
|
continue
|
||||||
|
for game in payload.get("games", []) or []:
|
||||||
|
start_utc = game.get("startTimeUTC")
|
||||||
|
if not start_utc:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
local_date = (
|
||||||
|
datetime.fromisoformat(start_utc.replace("Z", "+00:00"))
|
||||||
|
.astimezone(EASTERN)
|
||||||
|
.date()
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
current = observed.get(round_num)
|
||||||
|
if current is None or local_date < current:
|
||||||
|
observed[round_num] = local_date
|
||||||
|
|
||||||
|
for round_num, start_date in observed.items():
|
||||||
|
merged[str(round_num)] = start_date.isoformat()
|
||||||
|
|
||||||
|
if merged:
|
||||||
|
_put(ROUND_DATES_KEY, merged)
|
||||||
|
return merged or None
|
||||||
|
|
||||||
|
|
||||||
def get_playoff_start_date():
|
def get_round_start_date(round_num):
|
||||||
payload, _ = _get(META_KEY)
|
"""Return the Eastern date round `round_num` began, or None if unknown."""
|
||||||
if not payload or not payload.get("first_date"):
|
payload, _ = _get(ROUND_DATES_KEY)
|
||||||
|
if not payload:
|
||||||
|
return None
|
||||||
|
iso = payload.get(str(round_num))
|
||||||
|
if not iso:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
return date.fromisoformat(payload["first_date"])
|
return date.fromisoformat(iso)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def day_n(now=None):
|
def day_n_for_round(round_num, now=None):
|
||||||
"""Returns (day_n, day_total_estimate) or (None, None)."""
|
"""Day number within a playoff round (Day 1 = round's first game date).
|
||||||
start = get_playoff_start_date()
|
|
||||||
|
Returns the day number (>= 1) or None when the round hasn't been anchored.
|
||||||
|
"""
|
||||||
|
if round_num is None:
|
||||||
|
return None
|
||||||
|
start = get_round_start_date(round_num)
|
||||||
if start is None:
|
if start is None:
|
||||||
return None, None
|
return None
|
||||||
now = now or datetime.now(EASTERN)
|
now = now or datetime.now(EASTERN)
|
||||||
today = now.date()
|
n = (now.date() - start).days + 1
|
||||||
n = (today - start).days + 1
|
return n if n >= 1 else None
|
||||||
if n < 1:
|
|
||||||
return None, None
|
|
||||||
# Typical playoff length: ~60 days from first-round start through Cup final.
|
|
||||||
return n, 60
|
|
||||||
|
|||||||
+15
-5
@@ -8,11 +8,10 @@ from app.games import parse_games
|
|||||||
from app.playoff import today_meta
|
from app.playoff import today_meta
|
||||||
from app.bracket_view import build_bracket_view
|
from app.bracket_view import build_bracket_view
|
||||||
from app.playoff_cache import (
|
from app.playoff_cache import (
|
||||||
day_n as compute_day_n,
|
day_n_for_round,
|
||||||
fetch_series,
|
fetch_series,
|
||||||
get_bracket,
|
get_bracket,
|
||||||
parse_series_id,
|
parse_series_id,
|
||||||
record_start_date_if_missing,
|
|
||||||
refresh_bracket,
|
refresh_bracket,
|
||||||
)
|
)
|
||||||
from app.series_view import build_series_view
|
from app.series_view import build_series_view
|
||||||
@@ -22,6 +21,17 @@ from zoneinfo import ZoneInfo
|
|||||||
_EASTERN = ZoneInfo("America/New_York")
|
_EASTERN = ZoneInfo("America/New_York")
|
||||||
|
|
||||||
|
|
||||||
|
def _max_playoff_round(raw_games):
|
||||||
|
max_round = 0
|
||||||
|
for g in raw_games or []:
|
||||||
|
if g.get("gameType") != 3:
|
||||||
|
continue
|
||||||
|
r = (g.get("seriesStatus") or {}).get("round") or 0
|
||||||
|
if r > max_round:
|
||||||
|
max_round = r
|
||||||
|
return max_round or None
|
||||||
|
|
||||||
|
|
||||||
@app.route("/manifest.json")
|
@app.route("/manifest.json")
|
||||||
def manifest():
|
def manifest():
|
||||||
return send_from_directory(app.static_folder, "manifest.json")
|
return send_from_directory(app.static_folder, "manifest.json")
|
||||||
@@ -61,10 +71,10 @@ def get_scoreboard():
|
|||||||
|
|
||||||
if scoreboard_data:
|
if scoreboard_data:
|
||||||
raw_games = scoreboard_data.get("games", [])
|
raw_games = scoreboard_data.get("games", [])
|
||||||
record_start_date_if_missing(raw_games)
|
|
||||||
games = parse_games(scoreboard_data)
|
games = parse_games(scoreboard_data)
|
||||||
n, total = compute_day_n()
|
max_round = _max_playoff_round(raw_games)
|
||||||
meta = today_meta(raw_games, day_n=n, day_total=total)
|
n = day_n_for_round(max_round) if max_round else None
|
||||||
|
meta = today_meta(raw_games, day_n=n)
|
||||||
|
|
||||||
pinned = [g for g in games if g.get("Pinned")]
|
pinned = [g for g in games if g.get("Pinned")]
|
||||||
remaining = [g for g in games if not g.get("Pinned")]
|
remaining = [g for g in games if not g.get("Pinned")]
|
||||||
|
|||||||
+3
-1
@@ -4,7 +4,7 @@ import time
|
|||||||
import schedule
|
import schedule
|
||||||
|
|
||||||
from app.api import refresh_scores
|
from app.api import refresh_scores
|
||||||
from app.playoff_cache import refresh_bracket
|
from app.playoff_cache import refresh_bracket, refresh_round_start_dates
|
||||||
from app.standings import refresh_standings
|
from app.standings import refresh_standings
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -14,8 +14,10 @@ def start_scheduler():
|
|||||||
schedule.every(600).seconds.do(refresh_standings)
|
schedule.every(600).seconds.do(refresh_standings)
|
||||||
schedule.every(10).seconds.do(refresh_scores)
|
schedule.every(10).seconds.do(refresh_scores)
|
||||||
schedule.every(3600).seconds.do(refresh_bracket)
|
schedule.every(3600).seconds.do(refresh_bracket)
|
||||||
|
schedule.every(21600).seconds.do(refresh_round_start_dates)
|
||||||
# Populate the cache once at startup so the banner has data immediately.
|
# Populate the cache once at startup so the banner has data immediately.
|
||||||
refresh_bracket()
|
refresh_bracket()
|
||||||
|
refresh_round_start_dates()
|
||||||
logger.info("Background scheduler started")
|
logger.info("Background scheduler started")
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -58,8 +58,7 @@ function applyMeta(meta) {
|
|||||||
|
|
||||||
const dayEl = banner.querySelector('.meta-day');
|
const dayEl = banner.querySelector('.meta-day');
|
||||||
if (meta.day_n != null) {
|
if (meta.day_n != null) {
|
||||||
const total = meta.day_total ? ` of ~${meta.day_total}` : '';
|
setText(dayEl, `Day ${meta.day_n}`);
|
||||||
setText(dayEl, `Day ${meta.day_n}${total}`);
|
|
||||||
dayEl.classList.remove('hidden');
|
dayEl.classList.remove('hidden');
|
||||||
} else {
|
} else {
|
||||||
dayEl.classList.add('hidden');
|
dayEl.classList.add('hidden');
|
||||||
|
|||||||
+97
-31
@@ -144,46 +144,112 @@ class TestFetchSeries:
|
|||||||
assert playoff_cache.fetch_series("2026-A") is None
|
assert playoff_cache.fetch_series("2026-A") is None
|
||||||
|
|
||||||
|
|
||||||
class TestRecordStartDate:
|
class TestRefreshRoundStartDates:
|
||||||
def test_no_playoff_games_no_write(self, tmp_db):
|
def test_no_bracket_returns_none(self, tmp_db):
|
||||||
result = playoff_cache.record_start_date_if_missing([{"gameType": 2}])
|
assert playoff_cache.refresh_round_start_dates(2026) is None
|
||||||
assert result is None
|
|
||||||
assert playoff_cache.get_playoff_start_date() is None
|
|
||||||
|
|
||||||
def test_records_on_first_playoff_sighting(self, tmp_db):
|
def test_anchors_round_to_earliest_game(self, tmp_db, monkeypatch):
|
||||||
now = datetime(2026, 4, 18, 20, 0, tzinfo=EASTERN)
|
playoff_cache._put(
|
||||||
result = playoff_cache.record_start_date_if_missing([{"gameType": 3}], now=now)
|
playoff_cache.bracket_key(2026),
|
||||||
assert result == "2026-04-18"
|
{"series": [{"seriesLetter": "A", "playoffRound": 1}]},
|
||||||
assert playoff_cache.get_playoff_start_date().isoformat() == "2026-04-18"
|
|
||||||
|
|
||||||
def test_idempotent_after_first_write(self, tmp_db):
|
|
||||||
first_now = datetime(2026, 4, 18, 20, 0, tzinfo=EASTERN)
|
|
||||||
second_now = datetime(2026, 4, 25, 20, 0, tzinfo=EASTERN)
|
|
||||||
playoff_cache.record_start_date_if_missing([{"gameType": 3}], now=first_now)
|
|
||||||
# Second call should not overwrite
|
|
||||||
result = playoff_cache.record_start_date_if_missing(
|
|
||||||
[{"gameType": 3}], now=second_now
|
|
||||||
)
|
)
|
||||||
assert result == "2026-04-18"
|
series_payload = {
|
||||||
|
"games": [
|
||||||
|
{"startTimeUTC": "2026-04-19T23:00:00Z"},
|
||||||
|
{"startTimeUTC": "2026-04-18T23:00:00Z"},
|
||||||
|
{"startTimeUTC": "2026-04-21T23:00:00Z"},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"app.playoff_cache.requests.get",
|
||||||
|
lambda *a, **kw: _Resp(series_payload),
|
||||||
|
)
|
||||||
|
|
||||||
|
merged = playoff_cache.refresh_round_start_dates(2026)
|
||||||
|
assert merged == {"1": "2026-04-18"}
|
||||||
|
assert playoff_cache.get_round_start_date(1).isoformat() == "2026-04-18"
|
||||||
|
|
||||||
|
def test_merges_multiple_rounds_min_per_round(self, tmp_db, monkeypatch):
|
||||||
|
playoff_cache._put(
|
||||||
|
playoff_cache.bracket_key(2026),
|
||||||
|
{
|
||||||
|
"series": [
|
||||||
|
{"seriesLetter": "A", "playoffRound": 1},
|
||||||
|
{"seriesLetter": "B", "playoffRound": 1},
|
||||||
|
{"seriesLetter": "I", "playoffRound": 2},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
payloads = {
|
||||||
|
"A": {"games": [{"startTimeUTC": "2026-04-19T23:00:00Z"}]},
|
||||||
|
"B": {"games": [{"startTimeUTC": "2026-04-18T23:00:00Z"}]},
|
||||||
|
"I": {"games": [{"startTimeUTC": "2026-04-29T23:00:00Z"}]},
|
||||||
|
}
|
||||||
|
|
||||||
|
def fake_get(url, *a, **kw):
|
||||||
|
letter = url.rstrip("/").rsplit("/", 1)[-1].upper()
|
||||||
|
return _Resp(payloads[letter])
|
||||||
|
|
||||||
|
monkeypatch.setattr("app.playoff_cache.requests.get", fake_get)
|
||||||
|
|
||||||
|
merged = playoff_cache.refresh_round_start_dates(2026)
|
||||||
|
assert merged == {"1": "2026-04-18", "2": "2026-04-29"}
|
||||||
|
|
||||||
|
def test_preserves_existing_rounds_on_merge(self, tmp_db, monkeypatch):
|
||||||
|
playoff_cache._put(
|
||||||
|
playoff_cache.ROUND_DATES_KEY, {"1": "2026-04-18", "2": "2026-04-29"}
|
||||||
|
)
|
||||||
|
playoff_cache._put(
|
||||||
|
playoff_cache.bracket_key(2026),
|
||||||
|
{"series": [{"seriesLetter": "M", "playoffRound": 3}]},
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"app.playoff_cache.requests.get",
|
||||||
|
lambda *a, **kw: _Resp(
|
||||||
|
{"games": [{"startTimeUTC": "2026-05-15T23:00:00Z"}]}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
merged = playoff_cache.refresh_round_start_dates(2026)
|
||||||
|
assert merged["1"] == "2026-04-18"
|
||||||
|
assert merged["2"] == "2026-04-29"
|
||||||
|
assert merged["3"] == "2026-05-15"
|
||||||
|
|
||||||
|
|
||||||
class TestDayN:
|
class TestDayNForRound:
|
||||||
def test_no_start_date(self, tmp_db):
|
def test_no_round_num(self, tmp_db):
|
||||||
assert playoff_cache.day_n() == (None, None)
|
assert playoff_cache.day_n_for_round(None) is None
|
||||||
|
|
||||||
|
def test_round_not_anchored(self, tmp_db):
|
||||||
|
assert playoff_cache.day_n_for_round(1) is None
|
||||||
|
|
||||||
def test_day_one(self, tmp_db):
|
def test_day_one(self, tmp_db):
|
||||||
|
playoff_cache._put(playoff_cache.ROUND_DATES_KEY, {"1": "2026-04-18"})
|
||||||
now = datetime(2026, 4, 18, 20, 0, tzinfo=EASTERN)
|
now = datetime(2026, 4, 18, 20, 0, tzinfo=EASTERN)
|
||||||
playoff_cache.record_start_date_if_missing([{"gameType": 3}], now=now)
|
assert playoff_cache.day_n_for_round(1, now=now) == 1
|
||||||
n, total = playoff_cache.day_n(now=now)
|
|
||||||
assert n == 1
|
def test_day_two(self, tmp_db):
|
||||||
assert total == 60
|
playoff_cache._put(playoff_cache.ROUND_DATES_KEY, {"1": "2026-04-18"})
|
||||||
|
now = datetime(2026, 4, 19, 10, 0, tzinfo=EASTERN)
|
||||||
|
assert playoff_cache.day_n_for_round(1, now=now) == 2
|
||||||
|
|
||||||
def test_day_five(self, tmp_db):
|
def test_day_five(self, tmp_db):
|
||||||
start = datetime(2026, 4, 18, 20, 0, tzinfo=EASTERN)
|
playoff_cache._put(playoff_cache.ROUND_DATES_KEY, {"1": "2026-04-18"})
|
||||||
later = datetime(2026, 4, 22, 20, 0, tzinfo=EASTERN)
|
now = datetime(2026, 4, 22, 20, 0, tzinfo=EASTERN)
|
||||||
playoff_cache.record_start_date_if_missing([{"gameType": 3}], now=start)
|
assert playoff_cache.day_n_for_round(1, now=now) == 5
|
||||||
n, _ = playoff_cache.day_n(now=later)
|
|
||||||
assert n == 5
|
def test_round_two_resets_to_day_one(self, tmp_db):
|
||||||
|
playoff_cache._put(
|
||||||
|
playoff_cache.ROUND_DATES_KEY,
|
||||||
|
{"1": "2026-04-18", "2": "2026-04-29"},
|
||||||
|
)
|
||||||
|
now = datetime(2026, 4, 29, 20, 0, tzinfo=EASTERN)
|
||||||
|
assert playoff_cache.day_n_for_round(2, now=now) == 1
|
||||||
|
|
||||||
|
def test_before_start_returns_none(self, tmp_db):
|
||||||
|
playoff_cache._put(playoff_cache.ROUND_DATES_KEY, {"2": "2026-04-29"})
|
||||||
|
now = datetime(2026, 4, 20, 20, 0, tzinfo=EASTERN)
|
||||||
|
assert playoff_cache.day_n_for_round(2, now=now) is None
|
||||||
|
|
||||||
|
|
||||||
class TestSchema:
|
class TestSchema:
|
||||||
|
|||||||
+33
-5
@@ -3,10 +3,15 @@ import pytest
|
|||||||
from app.scheduler import start_scheduler
|
from app.scheduler import start_scheduler
|
||||||
|
|
||||||
|
|
||||||
|
def _patch_eager(mocker):
|
||||||
|
mocker.patch("app.scheduler.refresh_bracket")
|
||||||
|
mocker.patch("app.scheduler.refresh_round_start_dates")
|
||||||
|
|
||||||
|
|
||||||
class TestStartScheduler:
|
class TestStartScheduler:
|
||||||
def test_registers_standings_refresh_every_600_seconds(self, mocker):
|
def test_registers_standings_refresh_every_600_seconds(self, mocker):
|
||||||
mock_schedule = mocker.patch("app.scheduler.schedule")
|
mock_schedule = mocker.patch("app.scheduler.schedule")
|
||||||
mocker.patch("app.scheduler.refresh_bracket")
|
_patch_eager(mocker)
|
||||||
mocker.patch("app.scheduler.time.sleep", side_effect=StopIteration)
|
mocker.patch("app.scheduler.time.sleep", side_effect=StopIteration)
|
||||||
|
|
||||||
with pytest.raises(StopIteration):
|
with pytest.raises(StopIteration):
|
||||||
@@ -17,7 +22,7 @@ class TestStartScheduler:
|
|||||||
|
|
||||||
def test_registers_score_refresh_every_10_seconds(self, mocker):
|
def test_registers_score_refresh_every_10_seconds(self, mocker):
|
||||||
mock_schedule = mocker.patch("app.scheduler.schedule")
|
mock_schedule = mocker.patch("app.scheduler.schedule")
|
||||||
mocker.patch("app.scheduler.refresh_bracket")
|
_patch_eager(mocker)
|
||||||
mocker.patch("app.scheduler.time.sleep", side_effect=StopIteration)
|
mocker.patch("app.scheduler.time.sleep", side_effect=StopIteration)
|
||||||
|
|
||||||
with pytest.raises(StopIteration):
|
with pytest.raises(StopIteration):
|
||||||
@@ -28,7 +33,7 @@ class TestStartScheduler:
|
|||||||
|
|
||||||
def test_registers_bracket_refresh_every_3600_seconds(self, mocker):
|
def test_registers_bracket_refresh_every_3600_seconds(self, mocker):
|
||||||
mock_schedule = mocker.patch("app.scheduler.schedule")
|
mock_schedule = mocker.patch("app.scheduler.schedule")
|
||||||
mocker.patch("app.scheduler.refresh_bracket")
|
_patch_eager(mocker)
|
||||||
mocker.patch("app.scheduler.time.sleep", side_effect=StopIteration)
|
mocker.patch("app.scheduler.time.sleep", side_effect=StopIteration)
|
||||||
|
|
||||||
with pytest.raises(StopIteration):
|
with pytest.raises(StopIteration):
|
||||||
@@ -37,9 +42,32 @@ class TestStartScheduler:
|
|||||||
intervals = [call[0][0] for call in mock_schedule.every.call_args_list]
|
intervals = [call[0][0] for call in mock_schedule.every.call_args_list]
|
||||||
assert 3600 in intervals
|
assert 3600 in intervals
|
||||||
|
|
||||||
|
def test_registers_round_start_dates_refresh_every_21600_seconds(self, mocker):
|
||||||
|
mock_schedule = mocker.patch("app.scheduler.schedule")
|
||||||
|
_patch_eager(mocker)
|
||||||
|
mocker.patch("app.scheduler.time.sleep", side_effect=StopIteration)
|
||||||
|
|
||||||
|
with pytest.raises(StopIteration):
|
||||||
|
start_scheduler()
|
||||||
|
|
||||||
|
intervals = [call[0][0] for call in mock_schedule.every.call_args_list]
|
||||||
|
assert 21600 in intervals
|
||||||
|
|
||||||
def test_invokes_bracket_refresh_eagerly_at_startup(self, mocker):
|
def test_invokes_bracket_refresh_eagerly_at_startup(self, mocker):
|
||||||
mocker.patch("app.scheduler.schedule")
|
mocker.patch("app.scheduler.schedule")
|
||||||
eager = mocker.patch("app.scheduler.refresh_bracket")
|
eager = mocker.patch("app.scheduler.refresh_bracket")
|
||||||
|
mocker.patch("app.scheduler.refresh_round_start_dates")
|
||||||
|
mocker.patch("app.scheduler.time.sleep", side_effect=StopIteration)
|
||||||
|
|
||||||
|
with pytest.raises(StopIteration):
|
||||||
|
start_scheduler()
|
||||||
|
|
||||||
|
assert eager.called
|
||||||
|
|
||||||
|
def test_invokes_round_start_dates_refresh_eagerly_at_startup(self, mocker):
|
||||||
|
mocker.patch("app.scheduler.schedule")
|
||||||
|
mocker.patch("app.scheduler.refresh_bracket")
|
||||||
|
eager = mocker.patch("app.scheduler.refresh_round_start_dates")
|
||||||
mocker.patch("app.scheduler.time.sleep", side_effect=StopIteration)
|
mocker.patch("app.scheduler.time.sleep", side_effect=StopIteration)
|
||||||
|
|
||||||
with pytest.raises(StopIteration):
|
with pytest.raises(StopIteration):
|
||||||
@@ -49,7 +77,7 @@ class TestStartScheduler:
|
|||||||
|
|
||||||
def test_runs_pending_on_each_tick(self, mocker):
|
def test_runs_pending_on_each_tick(self, mocker):
|
||||||
mock_schedule = mocker.patch("app.scheduler.schedule")
|
mock_schedule = mocker.patch("app.scheduler.schedule")
|
||||||
mocker.patch("app.scheduler.refresh_bracket")
|
_patch_eager(mocker)
|
||||||
call_count = {"n": 0}
|
call_count = {"n": 0}
|
||||||
|
|
||||||
def sleep_twice(_):
|
def sleep_twice(_):
|
||||||
@@ -66,7 +94,7 @@ class TestStartScheduler:
|
|||||||
|
|
||||||
def test_continues_after_exception_in_run_pending(self, mocker):
|
def test_continues_after_exception_in_run_pending(self, mocker):
|
||||||
mock_schedule = mocker.patch("app.scheduler.schedule")
|
mock_schedule = mocker.patch("app.scheduler.schedule")
|
||||||
mocker.patch("app.scheduler.refresh_bracket")
|
_patch_eager(mocker)
|
||||||
call_count = {"n": 0}
|
call_count = {"n": 0}
|
||||||
|
|
||||||
def raise_then_stop(_):
|
def raise_then_stop(_):
|
||||||
|
|||||||
Reference in New Issue
Block a user