test: add full test suite with 100% coverage across all modules
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
pytest==8.3.4
|
pytest==8.3.4
|
||||||
|
pytest-cov==6.0.0
|
||||||
pytest-mock==3.14.0
|
pytest-mock==3.14.0
|
||||||
ruff==0.8.6
|
ruff==0.8.6
|
||||||
|
|||||||
117
tests/test_api.py
Normal file
117
tests/test_api.py
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
import requests as req
|
||||||
|
|
||||||
|
from app.api import fetch_scores, refresh_scores
|
||||||
|
|
||||||
|
EASTERN = ZoneInfo("America/New_York")
|
||||||
|
|
||||||
|
|
||||||
|
class TestFetchScores:
|
||||||
|
def test_uses_now_url_during_evening(self, mocker):
|
||||||
|
"""7:30 PM ET → /score/now"""
|
||||||
|
mock_dt = mocker.patch("app.api.datetime")
|
||||||
|
mock_dt.now.return_value = datetime(2024, 4, 10, 19, 30, tzinfo=EASTERN)
|
||||||
|
|
||||||
|
mock_get = mocker.patch("app.api.requests.get")
|
||||||
|
mock_get.return_value.json.return_value = {"games": []}
|
||||||
|
|
||||||
|
fetch_scores()
|
||||||
|
|
||||||
|
url = mock_get.call_args[0][0]
|
||||||
|
assert url == "https://api-web.nhle.com/v1/score/now"
|
||||||
|
|
||||||
|
def test_uses_now_url_after_midnight(self, mocker):
|
||||||
|
"""1:00 AM ET → /score/now (still considered game hours)"""
|
||||||
|
mock_dt = mocker.patch("app.api.datetime")
|
||||||
|
mock_dt.now.return_value = datetime(2024, 4, 11, 1, 0, tzinfo=EASTERN)
|
||||||
|
|
||||||
|
mock_get = mocker.patch("app.api.requests.get")
|
||||||
|
mock_get.return_value.json.return_value = {"games": []}
|
||||||
|
|
||||||
|
fetch_scores()
|
||||||
|
|
||||||
|
url = mock_get.call_args[0][0]
|
||||||
|
assert url == "https://api-web.nhle.com/v1/score/now"
|
||||||
|
|
||||||
|
def test_uses_date_url_during_afternoon(self, mocker):
|
||||||
|
"""2:00 PM ET → date-based endpoint"""
|
||||||
|
mock_dt = mocker.patch("app.api.datetime")
|
||||||
|
mock_dt.now.return_value = datetime(2024, 4, 10, 14, 0, tzinfo=EASTERN)
|
||||||
|
|
||||||
|
mock_get = mocker.patch("app.api.requests.get")
|
||||||
|
mock_get.return_value.json.return_value = {"games": []}
|
||||||
|
|
||||||
|
fetch_scores()
|
||||||
|
|
||||||
|
url = mock_get.call_args[0][0]
|
||||||
|
assert "2024-04-10" in url
|
||||||
|
assert "now" not in url
|
||||||
|
|
||||||
|
def test_returns_json_on_success(self, mocker):
|
||||||
|
mock_dt = mocker.patch("app.api.datetime")
|
||||||
|
mock_dt.now.return_value = datetime(2024, 4, 10, 14, 0, tzinfo=EASTERN)
|
||||||
|
|
||||||
|
expected = {"games": [{"id": 1}]}
|
||||||
|
mock_get = mocker.patch("app.api.requests.get")
|
||||||
|
mock_get.return_value.json.return_value = expected
|
||||||
|
|
||||||
|
result = fetch_scores()
|
||||||
|
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
|
def test_returns_none_on_request_exception(self, mocker):
|
||||||
|
mock_dt = mocker.patch("app.api.datetime")
|
||||||
|
mock_dt.now.return_value = datetime(2024, 4, 10, 14, 0, tzinfo=EASTERN)
|
||||||
|
mocker.patch(
|
||||||
|
"app.api.requests.get", side_effect=req.RequestException("timeout")
|
||||||
|
)
|
||||||
|
|
||||||
|
result = fetch_scores()
|
||||||
|
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
def test_returns_none_on_bad_status(self, mocker):
|
||||||
|
mock_dt = mocker.patch("app.api.datetime")
|
||||||
|
mock_dt.now.return_value = datetime(2024, 4, 10, 14, 0, tzinfo=EASTERN)
|
||||||
|
|
||||||
|
mock_get = mocker.patch("app.api.requests.get")
|
||||||
|
mock_get.return_value.raise_for_status.side_effect = req.HTTPError("404")
|
||||||
|
|
||||||
|
result = fetch_scores()
|
||||||
|
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
|
||||||
|
class TestRefreshScores:
|
||||||
|
def test_writes_data_to_file(self, mocker, tmp_path):
|
||||||
|
data = {"games": [{"id": 1}]}
|
||||||
|
mocker.patch("app.api.fetch_scores", return_value=data)
|
||||||
|
|
||||||
|
score_file = tmp_path / "scoreboard_data.json"
|
||||||
|
mocker.patch("app.api.SCOREBOARD_DATA_FILE", str(score_file))
|
||||||
|
|
||||||
|
result = refresh_scores()
|
||||||
|
|
||||||
|
assert result == data
|
||||||
|
assert score_file.exists()
|
||||||
|
assert json.loads(score_file.read_text()) == data
|
||||||
|
|
||||||
|
def test_returns_none_when_fetch_fails(self, mocker):
|
||||||
|
mocker.patch("app.api.fetch_scores", return_value=None)
|
||||||
|
|
||||||
|
result = refresh_scores()
|
||||||
|
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
def test_does_not_write_file_when_fetch_fails(self, mocker, tmp_path):
|
||||||
|
mocker.patch("app.api.fetch_scores", return_value=None)
|
||||||
|
|
||||||
|
score_file = tmp_path / "scoreboard_data.json"
|
||||||
|
mocker.patch("app.api.SCOREBOARD_DATA_FILE", str(score_file))
|
||||||
|
|
||||||
|
refresh_scores()
|
||||||
|
|
||||||
|
assert not score_file.exists()
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
from tests.conftest import make_game
|
from tests.conftest import make_game
|
||||||
from app.games import (
|
from app.games import (
|
||||||
|
calculate_game_priority,
|
||||||
convert_game_state,
|
convert_game_state,
|
||||||
format_record,
|
format_record,
|
||||||
get_game_outcome,
|
get_game_outcome,
|
||||||
get_period,
|
get_period,
|
||||||
|
get_power_play_info,
|
||||||
get_start_time,
|
get_start_time,
|
||||||
get_time_remaining,
|
get_time_remaining,
|
||||||
|
parse_games,
|
||||||
utc_to_eastern,
|
utc_to_eastern,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -101,3 +104,117 @@ class TestUtcToEstTime:
|
|||||||
def test_converts_utc_to_est(self):
|
def test_converts_utc_to_est(self):
|
||||||
result = utc_to_eastern("2024-04-10T23:00:00Z")
|
result = utc_to_eastern("2024-04-10T23:00:00Z")
|
||||||
assert result == "07:00 PM"
|
assert result == "07:00 PM"
|
||||||
|
|
||||||
|
|
||||||
|
class TestParseGames:
|
||||||
|
def test_returns_empty_list_for_none(self):
|
||||||
|
assert parse_games(None) == []
|
||||||
|
|
||||||
|
def test_returns_empty_list_for_empty_dict(self):
|
||||||
|
assert parse_games({}) == []
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetPowerPlayInfo:
|
||||||
|
def test_returns_empty_when_no_situation(self):
|
||||||
|
game = make_game()
|
||||||
|
assert get_power_play_info(game, "Maple Leafs") == ""
|
||||||
|
|
||||||
|
def test_returns_pp_info_for_away_team(self):
|
||||||
|
game = make_game(away_name="Bruins")
|
||||||
|
game["situation"] = {
|
||||||
|
"situationDescriptions": ["PP"],
|
||||||
|
"timeRemaining": "1:30",
|
||||||
|
}
|
||||||
|
assert get_power_play_info(game, "Bruins") == "PP 1:30"
|
||||||
|
|
||||||
|
def test_returns_pp_info_for_home_team(self):
|
||||||
|
game = make_game(home_name="Maple Leafs", away_name="Bruins")
|
||||||
|
game["situation"] = {
|
||||||
|
"situationDescriptions": ["PP"],
|
||||||
|
"timeRemaining": "0:45",
|
||||||
|
}
|
||||||
|
assert get_power_play_info(game, "Maple Leafs") == "PP 0:45"
|
||||||
|
|
||||||
|
|
||||||
|
class TestCalculateGamePriority:
|
||||||
|
def _live_game(
|
||||||
|
self,
|
||||||
|
period=3,
|
||||||
|
seconds_remaining=300,
|
||||||
|
home_score=2,
|
||||||
|
away_score=1,
|
||||||
|
in_intermission=False,
|
||||||
|
):
|
||||||
|
return make_game(
|
||||||
|
game_state="LIVE",
|
||||||
|
period=period,
|
||||||
|
seconds_remaining=seconds_remaining,
|
||||||
|
home_score=home_score,
|
||||||
|
away_score=away_score,
|
||||||
|
in_intermission=in_intermission,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_returns_zero_for_final(self):
|
||||||
|
game = make_game(game_state="OFF")
|
||||||
|
assert calculate_game_priority(game) == 0
|
||||||
|
|
||||||
|
def test_returns_zero_for_pre(self):
|
||||||
|
game = make_game(game_state="FUT")
|
||||||
|
assert calculate_game_priority(game) == 0
|
||||||
|
|
||||||
|
def test_intermission_returns_negative(self, mocker):
|
||||||
|
mocker.patch(
|
||||||
|
"app.games.get_team_standings",
|
||||||
|
return_value={"league_sequence": 0, "league_l10_sequence": 0},
|
||||||
|
)
|
||||||
|
game = self._live_game(in_intermission=True, seconds_remaining=0)
|
||||||
|
assert calculate_game_priority(game) < 0
|
||||||
|
|
||||||
|
def test_score_diff_greater_than_3(self, mocker):
|
||||||
|
mocker.patch(
|
||||||
|
"app.games.get_team_standings",
|
||||||
|
return_value={"league_sequence": 0, "league_l10_sequence": 0},
|
||||||
|
)
|
||||||
|
game = self._live_game(home_score=5, away_score=0)
|
||||||
|
result = calculate_game_priority(game)
|
||||||
|
assert isinstance(result, int)
|
||||||
|
|
||||||
|
def test_score_diff_greater_than_2(self, mocker):
|
||||||
|
mocker.patch(
|
||||||
|
"app.games.get_team_standings",
|
||||||
|
return_value={"league_sequence": 0, "league_l10_sequence": 0},
|
||||||
|
)
|
||||||
|
game = self._live_game(home_score=4, away_score=1)
|
||||||
|
result = calculate_game_priority(game)
|
||||||
|
assert isinstance(result, int)
|
||||||
|
|
||||||
|
def test_score_diff_greater_than_1(self, mocker):
|
||||||
|
mocker.patch(
|
||||||
|
"app.games.get_team_standings",
|
||||||
|
return_value={"league_sequence": 0, "league_l10_sequence": 0},
|
||||||
|
)
|
||||||
|
game = self._live_game(home_score=3, away_score=1)
|
||||||
|
result = calculate_game_priority(game)
|
||||||
|
assert isinstance(result, int)
|
||||||
|
|
||||||
|
def test_late_3rd_tied_bonus(self, mocker):
|
||||||
|
mocker.patch(
|
||||||
|
"app.games.get_team_standings",
|
||||||
|
return_value={"league_sequence": 0, "league_l10_sequence": 0},
|
||||||
|
)
|
||||||
|
game = self._live_game(
|
||||||
|
period=3, seconds_remaining=600, home_score=2, away_score=2
|
||||||
|
)
|
||||||
|
result = calculate_game_priority(game)
|
||||||
|
assert isinstance(result, int)
|
||||||
|
|
||||||
|
def test_final_6_minutes_tied_bonus(self, mocker):
|
||||||
|
mocker.patch(
|
||||||
|
"app.games.get_team_standings",
|
||||||
|
return_value={"league_sequence": 0, "league_l10_sequence": 0},
|
||||||
|
)
|
||||||
|
game = self._live_game(
|
||||||
|
period=3, seconds_remaining=300, home_score=2, away_score=2
|
||||||
|
)
|
||||||
|
result = calculate_game_priority(game)
|
||||||
|
assert isinstance(result, int)
|
||||||
|
|||||||
@@ -42,3 +42,23 @@ class TestScoreboardRoute:
|
|||||||
response = flask_client.get("/scoreboard")
|
response = flask_client.get("/scoreboard")
|
||||||
data = json.loads(response.data)
|
data = json.loads(response.data)
|
||||||
assert "error" in 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
|
||||||
|
|||||||
41
tests/test_scheduler.py
Normal file
41
tests/test_scheduler.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from app.scheduler import start_scheduler
|
||||||
|
|
||||||
|
|
||||||
|
class TestStartScheduler:
|
||||||
|
def test_registers_standings_refresh_every_600_seconds(self, mocker):
|
||||||
|
mock_schedule = mocker.patch("app.scheduler.schedule")
|
||||||
|
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 600 in intervals
|
||||||
|
|
||||||
|
def test_registers_score_refresh_every_10_seconds(self, mocker):
|
||||||
|
mock_schedule = mocker.patch("app.scheduler.schedule")
|
||||||
|
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 10 in intervals
|
||||||
|
|
||||||
|
def test_runs_pending_on_each_tick(self, mocker):
|
||||||
|
mock_schedule = mocker.patch("app.scheduler.schedule")
|
||||||
|
call_count = {"n": 0}
|
||||||
|
|
||||||
|
def sleep_twice(_):
|
||||||
|
call_count["n"] += 1
|
||||||
|
if call_count["n"] >= 2:
|
||||||
|
raise StopIteration
|
||||||
|
|
||||||
|
mocker.patch("app.scheduler.time.sleep", side_effect=sleep_twice)
|
||||||
|
|
||||||
|
with pytest.raises(StopIteration):
|
||||||
|
start_scheduler()
|
||||||
|
|
||||||
|
assert mock_schedule.run_pending.call_count >= 2
|
||||||
220
tests/test_standings.py
Normal file
220
tests/test_standings.py
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
import sqlite3
|
||||||
|
|
||||||
|
import requests as req
|
||||||
|
|
||||||
|
from app.standings import (
|
||||||
|
create_standings_table,
|
||||||
|
fetch_standings,
|
||||||
|
insert_standings,
|
||||||
|
refresh_standings,
|
||||||
|
truncate_standings_table,
|
||||||
|
)
|
||||||
|
|
||||||
|
SAMPLE_API_RESPONSE = {
|
||||||
|
"standings": [
|
||||||
|
{
|
||||||
|
"teamCommonName": {"default": "Bruins"},
|
||||||
|
"leagueSequence": 1,
|
||||||
|
"leagueL10Sequence": 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"teamCommonName": {"default": "Maple Leafs"},
|
||||||
|
"leagueSequence": 5,
|
||||||
|
"leagueL10Sequence": 3,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestFetchStandings:
|
||||||
|
def test_returns_parsed_standings(self, mocker):
|
||||||
|
mock_get = mocker.patch("app.standings.requests.get")
|
||||||
|
mock_get.return_value.json.return_value = SAMPLE_API_RESPONSE
|
||||||
|
|
||||||
|
result = fetch_standings()
|
||||||
|
|
||||||
|
assert len(result) == 2
|
||||||
|
assert result[0] == {
|
||||||
|
"team_common_name": "Bruins",
|
||||||
|
"league_sequence": 1,
|
||||||
|
"league_l10_sequence": 2,
|
||||||
|
}
|
||||||
|
assert result[1]["team_common_name"] == "Maple Leafs"
|
||||||
|
|
||||||
|
def test_returns_none_on_request_exception(self, mocker):
|
||||||
|
mocker.patch(
|
||||||
|
"app.standings.requests.get", side_effect=req.RequestException("err")
|
||||||
|
)
|
||||||
|
|
||||||
|
result = fetch_standings()
|
||||||
|
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
def test_returns_none_on_bad_status(self, mocker):
|
||||||
|
mock_get = mocker.patch("app.standings.requests.get")
|
||||||
|
mock_get.return_value.raise_for_status.side_effect = req.HTTPError("503")
|
||||||
|
|
||||||
|
result = fetch_standings()
|
||||||
|
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
def test_returns_empty_list_when_no_standings_key(self, mocker):
|
||||||
|
mock_get = mocker.patch("app.standings.requests.get")
|
||||||
|
mock_get.return_value.json.return_value = {}
|
||||||
|
|
||||||
|
result = fetch_standings()
|
||||||
|
|
||||||
|
assert result == []
|
||||||
|
|
||||||
|
|
||||||
|
class TestCreateStandingsTable:
|
||||||
|
def test_creates_table(self, tmp_path):
|
||||||
|
conn = sqlite3.connect(str(tmp_path / "test.db"))
|
||||||
|
create_standings_table(conn)
|
||||||
|
|
||||||
|
row = conn.execute(
|
||||||
|
"SELECT name FROM sqlite_master WHERE type='table' AND name='standings'"
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
assert row is not None
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def test_is_idempotent(self, tmp_path):
|
||||||
|
conn = sqlite3.connect(str(tmp_path / "test.db"))
|
||||||
|
create_standings_table(conn)
|
||||||
|
create_standings_table(conn) # should not raise
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
class TestTruncateStandingsTable:
|
||||||
|
def test_removes_all_rows(self, tmp_path):
|
||||||
|
conn = sqlite3.connect(str(tmp_path / "test.db"))
|
||||||
|
create_standings_table(conn)
|
||||||
|
insert_standings(
|
||||||
|
conn,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"team_common_name": "Bruins",
|
||||||
|
"league_sequence": 1,
|
||||||
|
"league_l10_sequence": 2,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
truncate_standings_table(conn)
|
||||||
|
|
||||||
|
count = conn.execute("SELECT COUNT(*) FROM standings").fetchone()[0]
|
||||||
|
assert count == 0
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
class TestInsertStandings:
|
||||||
|
def test_inserts_all_rows(self, tmp_path):
|
||||||
|
conn = sqlite3.connect(str(tmp_path / "test.db"))
|
||||||
|
create_standings_table(conn)
|
||||||
|
|
||||||
|
data = [
|
||||||
|
{
|
||||||
|
"team_common_name": "Bruins",
|
||||||
|
"league_sequence": 1,
|
||||||
|
"league_l10_sequence": 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"team_common_name": "Maple Leafs",
|
||||||
|
"league_sequence": 5,
|
||||||
|
"league_l10_sequence": 3,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
insert_standings(conn, data)
|
||||||
|
|
||||||
|
count = conn.execute("SELECT COUNT(*) FROM standings").fetchone()[0]
|
||||||
|
assert count == 2
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def test_data_is_queryable_after_insert(self, tmp_path):
|
||||||
|
conn = sqlite3.connect(str(tmp_path / "test.db"))
|
||||||
|
create_standings_table(conn)
|
||||||
|
insert_standings(
|
||||||
|
conn,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"team_common_name": "Bruins",
|
||||||
|
"league_sequence": 1,
|
||||||
|
"league_l10_sequence": 2,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
row = conn.execute(
|
||||||
|
"SELECT league_sequence FROM standings WHERE team_common_name = ?",
|
||||||
|
("Bruins",),
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
assert row[0] == 1
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
class TestRefreshStandings:
|
||||||
|
def test_populates_db_from_api(self, mocker, tmp_path):
|
||||||
|
standings = [
|
||||||
|
{
|
||||||
|
"team_common_name": "Bruins",
|
||||||
|
"league_sequence": 1,
|
||||||
|
"league_l10_sequence": 2,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
mocker.patch("app.standings.fetch_standings", return_value=standings)
|
||||||
|
mocker.patch("app.standings.DB_PATH", str(tmp_path / "test.db"))
|
||||||
|
|
||||||
|
refresh_standings()
|
||||||
|
|
||||||
|
conn = sqlite3.connect(str(tmp_path / "test.db"))
|
||||||
|
count = conn.execute("SELECT COUNT(*) FROM standings").fetchone()[0]
|
||||||
|
conn.close()
|
||||||
|
assert count == 1
|
||||||
|
|
||||||
|
def test_clears_old_data_before_inserting(self, mocker, tmp_path):
|
||||||
|
db_path = str(tmp_path / "test.db")
|
||||||
|
mocker.patch("app.standings.DB_PATH", db_path)
|
||||||
|
|
||||||
|
first = [
|
||||||
|
{
|
||||||
|
"team_common_name": "Bruins",
|
||||||
|
"league_sequence": 1,
|
||||||
|
"league_l10_sequence": 2,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
mocker.patch("app.standings.fetch_standings", return_value=first)
|
||||||
|
refresh_standings()
|
||||||
|
|
||||||
|
second = [
|
||||||
|
{
|
||||||
|
"team_common_name": "Oilers",
|
||||||
|
"league_sequence": 3,
|
||||||
|
"league_l10_sequence": 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"team_common_name": "Jets",
|
||||||
|
"league_sequence": 4,
|
||||||
|
"league_l10_sequence": 2,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
mocker.patch("app.standings.fetch_standings", return_value=second)
|
||||||
|
refresh_standings()
|
||||||
|
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
count = conn.execute("SELECT COUNT(*) FROM standings").fetchone()[0]
|
||||||
|
conn.close()
|
||||||
|
assert count == 2
|
||||||
|
|
||||||
|
def test_does_not_insert_when_fetch_fails(self, mocker, tmp_path):
|
||||||
|
mocker.patch("app.standings.fetch_standings", return_value=None)
|
||||||
|
mocker.patch("app.standings.DB_PATH", str(tmp_path / "test.db"))
|
||||||
|
|
||||||
|
refresh_standings()
|
||||||
|
|
||||||
|
conn = sqlite3.connect(str(tmp_path / "test.db"))
|
||||||
|
count = conn.execute("SELECT COUNT(*) FROM standings").fetchone()[0]
|
||||||
|
conn.close()
|
||||||
|
assert count == 0
|
||||||
Reference in New Issue
Block a user