steam-backlog-enforcer/steam_backlog_enforcer/tests/test_steam_api.py
Krzysztof kuhy Rudnicki 551b8a4f95 chore: set up as standalone repo
Extracted from testsAndMisc monorepo. Changes:
- Rewrote imports from python_pkg.steam_backlog_enforcer.* → steam_backlog_enforcer.*
- Moved run.sh, install.sh, README.md, service file to repo root
- Added standalone pyproject.toml, requirements.txt, .pre-commit-config.yaml, .gitignore
- Added GitHub Actions CI workflows (tests + pre-commit)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 07:21:29 +02:00

334 lines
11 KiB
Python

"""Tests for steam_api module."""
from __future__ import annotations
from typing import Any
from unittest.mock import MagicMock, patch
import pytest
import requests
from steam_backlog_enforcer.steam_api import (
AchievementInfo,
GameInfo,
SteamAPIClient,
SteamAPIError,
)
class TestAchievementInfo:
"""Tests for AchievementInfo."""
def test_create(self) -> None:
a = AchievementInfo(
api_name="ACH_1", display_name="First", achieved=True, unlock_time=1000
)
assert a.api_name == "ACH_1"
assert a.achieved is True
class TestGameInfo:
"""Tests for GameInfo."""
def test_completion_pct_zero_achievements(self) -> None:
g = GameInfo(
app_id=1,
name="G",
total_achievements=0,
unlocked_achievements=0,
playtime_minutes=0,
)
assert g.completion_pct == 100.0
def test_completion_pct_partial(self) -> None:
g = GameInfo(
app_id=1,
name="G",
total_achievements=10,
unlocked_achievements=5,
playtime_minutes=0,
)
assert g.completion_pct == 50.0
def test_is_complete_true(self) -> None:
g = GameInfo(
app_id=1,
name="G",
total_achievements=5,
unlocked_achievements=5,
playtime_minutes=0,
)
assert g.is_complete is True
def test_is_complete_false(self) -> None:
g = GameInfo(
app_id=1,
name="G",
total_achievements=5,
unlocked_achievements=3,
playtime_minutes=0,
)
assert g.is_complete is False
def test_is_complete_zero(self) -> None:
g = GameInfo(
app_id=1,
name="G",
total_achievements=0,
unlocked_achievements=0,
playtime_minutes=0,
)
assert g.is_complete is False
def test_to_snapshot(self) -> None:
ach = AchievementInfo(
api_name="A1", display_name="Ach1", achieved=True, unlock_time=99
)
g = GameInfo(
app_id=1,
name="G",
total_achievements=1,
unlocked_achievements=1,
playtime_minutes=60,
achievements=[ach],
completionist_hours=5.0,
)
snap = g.to_snapshot()
assert snap["app_id"] == 1
assert snap["achievements"][0]["api_name"] == "A1"
assert snap["completionist_hours"] == 5.0
def test_from_snapshot(self) -> None:
data: dict[str, Any] = {
"app_id": 2,
"name": "G2",
"total_achievements": 3,
"unlocked_achievements": 1,
"playtime_minutes": 120,
"completionist_hours": 10.0,
"achievements": [
{
"api_name": "A1",
"display_name": "First",
"achieved": False,
"unlock_time": 0,
},
],
}
g = GameInfo.from_snapshot(data)
assert g.app_id == 2
assert g.completionist_hours == 10.0
assert len(g.achievements) == 1
def test_from_snapshot_defaults(self) -> None:
data: dict[str, Any] = {
"app_id": 3,
"name": "G3",
"total_achievements": 0,
"unlocked_achievements": 0,
}
g = GameInfo.from_snapshot(data)
assert g.playtime_minutes == 0
assert g.completionist_hours == -1
assert g.achievements == []
def test_from_snapshot_achievement_defaults(self) -> None:
data: dict[str, Any] = {
"app_id": 4,
"name": "G4",
"total_achievements": 1,
"unlocked_achievements": 0,
"achievements": [{"api_name": "X", "achieved": False}],
}
g = GameInfo.from_snapshot(data)
assert g.achievements[0].display_name == "X"
assert g.achievements[0].unlock_time == 0
class TestSteamAPIClient:
"""Tests for SteamAPIClient."""
def test_init(self) -> None:
client = SteamAPIClient("key", "id")
assert client.api_key == "key"
assert client.steam_id == "id"
def test_rate_limit(self) -> None:
client = SteamAPIClient("key", "id")
# Should not block on first call
client._rate_limit()
def test_rate_limit_throttle(self) -> None:
client = SteamAPIClient("key", "id")
# Fill up the rate limit window
client._request_times = [__import__("time").time()] * client._max_rps
with patch(
"steam_backlog_enforcer.steam_api.time.sleep",
) as mock_sleep:
# Next call should trigger sleep then succeed
client._rate_limit()
mock_sleep.assert_called()
def test_get_success(self) -> None:
client = SteamAPIClient("key", "id")
mock_resp = MagicMock()
mock_resp.json.return_value = {"data": "value"}
client.session.get = MagicMock(return_value=mock_resp)
result = client._get("https://example.com/api")
assert result == {"data": "value"}
def test_get_with_params(self) -> None:
client = SteamAPIClient("key", "id")
mock_resp = MagicMock()
mock_resp.json.return_value = {"data": "value"}
client.session.get = MagicMock(return_value=mock_resp)
result = client._get("https://example.com/api", params={"foo": "bar"})
assert result == {"data": "value"}
# Verify key was added to existing params dict
call_kwargs = client.session.get.call_args
assert call_kwargs[1]["params"]["foo"] == "bar"
assert call_kwargs[1]["params"]["key"] == "key"
def test_get_failure(self) -> None:
client = SteamAPIClient("key", "id")
client.session.get = MagicMock(side_effect=requests.RequestException("fail"))
with pytest.raises(SteamAPIError):
client._get("https://example.com/api")
def test_get_owned_games(self) -> None:
client = SteamAPIClient("key", "id")
with patch.object(
client,
"_get",
return_value={"response": {"games": [{"appid": 440}]}},
):
games = client.get_owned_games()
assert len(games) == 1
assert games[0]["appid"] == 440
def test_get_owned_games_empty(self) -> None:
client = SteamAPIClient("key", "id")
with patch.object(client, "_get", return_value={"response": {}}):
games = client.get_owned_games()
assert games == []
def test_get_achievement_details(self) -> None:
client = SteamAPIClient("key", "id")
with patch.object(
client,
"_get",
return_value={
"playerstats": {
"success": True,
"achievements": [
{
"apiname": "ACH_1",
"name": "First",
"achieved": 1,
"unlocktime": 1000,
},
],
},
},
):
result = client.get_achievement_details(440)
assert len(result) == 1
assert result[0].achieved is True
def test_get_achievement_details_failure(self) -> None:
client = SteamAPIClient("key", "id")
with patch.object(client, "_get", side_effect=SteamAPIError("fail")):
result = client.get_achievement_details(440)
assert result == []
def test_get_achievement_details_not_success(self) -> None:
client = SteamAPIClient("key", "id")
with patch.object(
client,
"_get",
return_value={"playerstats": {"success": False}},
):
result = client.get_achievement_details(440)
assert result == []
def test_fetch_one_game(self) -> None:
client = SteamAPIClient("key", "id")
ach = AchievementInfo("A1", "Ach1", achieved=True, unlock_time=100)
with patch.object(client, "get_achievement_details", return_value=[ach]):
result = client._fetch_one_game(
{"appid": 440, "name": "TF2", "playtime_forever": 60},
)
assert result is not None
assert result.app_id == 440
def test_fetch_one_game_no_achievements(self) -> None:
client = SteamAPIClient("key", "id")
with patch.object(client, "get_achievement_details", return_value=[]):
result = client._fetch_one_game({"appid": 440})
assert result is None
def test_build_game_list(self) -> None:
client = SteamAPIClient("key", "id")
ach = AchievementInfo("A1", "Ach1", achieved=True, unlock_time=100)
with (
patch.object(
client,
"get_owned_games",
return_value=[{"appid": 440, "name": "TF2", "playtime_forever": 60}],
),
patch.object(client, "get_achievement_details", return_value=[ach]),
):
progress_calls: list[tuple[int, int]] = []
def progress(c: int, t: int) -> None:
progress_calls.append((c, t))
games = client.build_game_list(progress_callback=progress)
assert len(games) == 1
assert len(progress_calls) > 0
def test_build_game_list_no_achievements_excluded(self) -> None:
"""Games without achievements are excluded from results."""
client = SteamAPIClient("key", "id")
with (
patch.object(
client,
"get_owned_games",
return_value=[{"appid": 440, "name": "TF2"}],
),
patch.object(client, "get_achievement_details", return_value=[]),
):
games = client.build_game_list()
assert games == []
def test_build_game_list_exception_in_future(self) -> None:
client = SteamAPIClient("key", "id")
with (
patch.object(
client,
"get_owned_games",
return_value=[{"appid": 440, "name": "TF2"}],
),
patch.object(
client,
"get_achievement_details",
side_effect=SteamAPIError("err"),
),
):
games = client.build_game_list()
assert games == []
def test_refresh_single_game(self) -> None:
client = SteamAPIClient("key", "id")
ach = AchievementInfo("A1", "Ach1", achieved=True, unlock_time=100)
with patch.object(client, "get_achievement_details", return_value=[ach]):
result = client.refresh_single_game(440, "TF2", 60)
assert result is not None
assert result.unlocked_achievements == 1
def test_refresh_single_game_no_achievements(self) -> None:
client = SteamAPIClient("key", "id")
with patch.object(client, "get_achievement_details", return_value=[]):
result = client.refresh_single_game(440, "TF2")
assert result is None