testsAndMisc/python_pkg/steam_backlog_enforcer/tests/test_protondb.py
Krzysztof kuhy Rudnicki 9e66638fda fix: sync test paths, drop stale assertions, fix coverage gap
- linux_configuration/tests: update script paths after periodic_background/
  reorganisation (hosts_file_monitor, makepkg_capped, music_parallelism,
  shutdown_timer_monitor, usage_monitoring_installer_efficiency)

- test_i3blocks_efficiency.sh: remove checks for HEARTBEAT_INTERVAL_S and
  WARP_POLL_INTERVAL_S constants that no longer exist

- test_pacman_wrapper_security.sh: remove tests 20-21 (builtin time helpers /
  external date calls) that are no longer applicable; update path

- generate_hosts_file.sh: add sed unblock rules for delio.com.pl and
  loverslab.com to stay consistent with install.sh whitelist

- steam_backlog_enforcer/scanning.py: remove unplayable_reason arg from
  logger.info call (too many format args); drop matching test assertion

- steam_backlog_enforcer/tests/test_protondb.py: add
  test_unplayable_reason_no_trending_tier to restore 100% branch coverage
  on protondb.py line 97 (was previously covered indirectly)
2026-05-16 15:46:02 +02:00

273 lines
8.9 KiB
Python

"""Tests for protondb module."""
from __future__ import annotations
import asyncio
import json
from typing import TYPE_CHECKING, Any
from unittest.mock import AsyncMock, MagicMock, patch
import aiohttp
from python_pkg.steam_backlog_enforcer.protondb import (
HTTP_NOT_FOUND,
ProtonDBRating,
_fetch_batch,
_fetch_one,
_load_cache,
_rating_from_cache,
_rating_to_dict,
_save_cache,
fetch_protondb_ratings,
)
if TYPE_CHECKING:
from pathlib import Path
class TestProtonDBRating:
"""Tests for ProtonDBRating."""
def test_playable_native(self) -> None:
r = ProtonDBRating(app_id=1, tier="native")
assert r.is_playable is True
def test_playable_platinum(self) -> None:
r = ProtonDBRating(app_id=1, tier="platinum")
assert r.is_playable is True
def test_playable_gold(self) -> None:
r = ProtonDBRating(app_id=1, tier="gold")
assert r.is_playable is True
def test_not_playable_silver(self) -> None:
r = ProtonDBRating(app_id=1, tier="silver")
assert r.is_playable is False
def test_not_playable_bronze(self) -> None:
r = ProtonDBRating(app_id=1, tier="bronze")
assert r.is_playable is False
def test_not_playable_borked(self) -> None:
r = ProtonDBRating(app_id=1, tier="borked")
assert r.is_playable is False
def test_playable_no_data(self) -> None:
r = ProtonDBRating(app_id=1, tier="")
assert r.is_playable is True
def test_playable_pending(self) -> None:
r = ProtonDBRating(app_id=1, tier="pending")
assert r.is_playable is True
def test_gold_trending_silver(self) -> None:
r = ProtonDBRating(app_id=1, tier="gold", trending_tier="silver")
assert r.is_playable is True
def test_gold_trending_gold(self) -> None:
r = ProtonDBRating(app_id=1, tier="gold", trending_tier="gold")
assert r.is_playable is True
def test_silver_trending_gold(self) -> None:
r = ProtonDBRating(app_id=1, tier="silver", trending_tier="gold")
assert r.is_playable is True
def test_gold_no_trending(self) -> None:
r = ProtonDBRating(app_id=1, tier="gold", trending_tier="")
assert r.is_playable is True
def test_gold_trending_platinum(self) -> None:
r = ProtonDBRating(app_id=1, tier="gold", trending_tier="platinum")
assert r.is_playable is True
def test_gold_trending_unknown(self) -> None:
r = ProtonDBRating(app_id=1, tier="gold", trending_tier="unknown")
assert r.is_playable is False
def test_gold_trending_bronze(self) -> None:
r = ProtonDBRating(app_id=1, tier="gold", trending_tier="bronze")
assert r.is_playable is False
def test_unknown_tier(self) -> None:
r = ProtonDBRating(app_id=1, tier="unknown_tier")
assert r.is_playable is False
def test_unplayable_reason_no_trending_tier(self) -> None:
r = ProtonDBRating(app_id=1, tier="borked")
assert "tier<" in r.unplayable_reason
def test_unplayable_reason_for_silver_silver(self) -> None:
r = ProtonDBRating(app_id=1, tier="silver", trending_tier="silver")
assert "no gold tier" in r.unplayable_reason
def test_unplayable_reason_for_gold_bronze(self) -> None:
r = ProtonDBRating(app_id=1, tier="gold", trending_tier="bronze")
assert "below silver" in r.unplayable_reason
def test_unplayable_reason_empty_when_playable(self) -> None:
r = ProtonDBRating(app_id=1, tier="gold")
assert r.unplayable_reason == ""
class TestProtonDBCache:
"""Tests for cache I/O."""
def test_load_cache_exists(self, tmp_path: Path) -> None:
cache_file = tmp_path / "protondb_cache.json"
cache_file.write_text(json.dumps({"440": {"tier": "gold"}}), encoding="utf-8")
with patch(
"python_pkg.steam_backlog_enforcer.protondb.PROTONDB_CACHE_FILE",
cache_file,
):
result = _load_cache()
assert result == {"440": {"tier": "gold"}}
def test_load_cache_missing(self, tmp_path: Path) -> None:
cache_file = tmp_path / "nonexistent.json"
with patch(
"python_pkg.steam_backlog_enforcer.protondb.PROTONDB_CACHE_FILE",
cache_file,
):
assert _load_cache() == {}
def test_save_cache(self, tmp_path: Path) -> None:
cache_file = tmp_path / "protondb_cache.json"
config_dir = tmp_path
with (
patch(
"python_pkg.steam_backlog_enforcer.protondb.PROTONDB_CACHE_FILE",
cache_file,
),
patch("python_pkg.steam_backlog_enforcer.protondb.CONFIG_DIR", config_dir),
):
_save_cache({"440": {"tier": "gold"}})
assert cache_file.exists()
class TestRatingConversion:
"""Tests for rating serialization."""
def test_to_dict(self) -> None:
r = ProtonDBRating(
app_id=1,
tier="gold",
trending_tier="platinum",
score=0.9,
confidence="high",
total_reports=100,
)
d = _rating_to_dict(r)
assert d["tier"] == "gold"
assert d["total_reports"] == 100
def test_from_cache(self) -> None:
data: dict[str, Any] = {
"tier": "silver",
"trending_tier": "bronze",
"score": 0.5,
}
r = _rating_from_cache(440, data)
assert r.app_id == 440
assert r.tier == "silver"
assert r.trending_tier == "bronze"
def test_from_cache_defaults(self) -> None:
r = _rating_from_cache(440, {})
assert r.tier == ""
assert r.total_reports == 0
class TestFetchOne:
"""Tests for _fetch_one."""
def test_success(self) -> None:
mock_resp = AsyncMock()
mock_resp.status = 200
mock_resp.raise_for_status = MagicMock()
mock_resp.json = AsyncMock(
return_value={"tier": "gold", "trendingTier": "platinum"}
)
mock_resp.__aenter__ = AsyncMock(return_value=mock_resp)
mock_resp.__aexit__ = AsyncMock(return_value=False)
mock_session = MagicMock()
mock_session.get = MagicMock(return_value=mock_resp)
sem = asyncio.Semaphore(1)
result = asyncio.run(_fetch_one(mock_session, sem, 440))
assert result.tier == "gold"
def test_not_found(self) -> None:
mock_resp = AsyncMock()
mock_resp.status = HTTP_NOT_FOUND
mock_resp.__aenter__ = AsyncMock(return_value=mock_resp)
mock_resp.__aexit__ = AsyncMock(return_value=False)
mock_session = MagicMock()
mock_session.get = MagicMock(return_value=mock_resp)
sem = asyncio.Semaphore(1)
result = asyncio.run(_fetch_one(mock_session, sem, 440))
assert result.tier == ""
def test_client_error(self) -> None:
mock_resp = AsyncMock()
mock_resp.status = 200
mock_resp.raise_for_status = MagicMock(side_effect=aiohttp.ClientError)
mock_resp.__aenter__ = AsyncMock(return_value=mock_resp)
mock_resp.__aexit__ = AsyncMock(return_value=False)
mock_session = MagicMock()
mock_session.get = MagicMock(return_value=mock_resp)
sem = asyncio.Semaphore(1)
result = asyncio.run(_fetch_one(mock_session, sem, 440))
assert result.tier == ""
class TestFetchBatch:
"""Tests for _fetch_batch."""
def test_returns_ratings(self) -> None:
rating = ProtonDBRating(app_id=440, tier="gold")
with patch(
"python_pkg.steam_backlog_enforcer.protondb._fetch_one",
new_callable=AsyncMock,
return_value=rating,
):
result = asyncio.run(_fetch_batch([440]))
assert len(result) == 1
assert result[0].tier == "gold"
class TestFetchProtondbRatings:
"""Tests for fetch_protondb_ratings."""
def test_all_cached(self, tmp_path: Path) -> None:
cache_file = tmp_path / "protondb_cache.json"
cache_file.write_text(json.dumps({"440": {"tier": "gold"}}), encoding="utf-8")
with patch(
"python_pkg.steam_backlog_enforcer.protondb.PROTONDB_CACHE_FILE",
cache_file,
):
result = fetch_protondb_ratings([440])
assert 440 in result
assert result[440].tier == "gold"
def test_fetch_uncached(self, tmp_path: Path) -> None:
cache_file = tmp_path / "protondb_cache.json"
config_dir = tmp_path
with (
patch(
"python_pkg.steam_backlog_enforcer.protondb.PROTONDB_CACHE_FILE",
cache_file,
),
patch("python_pkg.steam_backlog_enforcer.protondb.CONFIG_DIR", config_dir),
patch(
"python_pkg.steam_backlog_enforcer.protondb._fetch_batch",
return_value=[ProtonDBRating(app_id=440, tier="platinum")],
),
):
result = fetch_protondb_ratings([440])
assert result[440].tier == "platinum"