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:
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