mirror of
https://github.com/kuhyx/steam-backlog-enforcer.git
synced 2026-11-11 12:47:32 +01:00
The pre-push ci-mirror runs the file-length gate over the whole tree, not the diff, so ~30 files left it red and no push could land. This clears the test half: 18 modules split into 60, plus the docstrings pylint wants on every class and test method. Splits are by responsibility, never mechanical partN names -- files are named for the first class or test they contain (test_stats_sum_hours, test_store_blocker_unblock_store, ...). Where a file was a single oversized class, its test methods are partitioned into sibling classes instead, with fixtures and helper classes copied into every part: a method-level split that drops an autouse fixture fails at runtime, not at import, so it is easy to miss. test_polls_tracking_part2 keeps its name only because it still holds one indivisible class; the rest of the partN files are gone. 1347 tests pass, unchanged in count and name from before the split -- the splitter was checked by diffing test-function names, since a test silently lost in a partition looks identical to one that never existed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016A1EYiv7e7HGhET84oa9M3
167 lines
5.1 KiB
Python
167 lines
5.1 KiB
Python
"""Tests for _web_dataset module — 100% branch coverage."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import replace
|
|
|
|
from steam_backlog_enforcer._web_dataset import (
|
|
WebGame,
|
|
_default_qualifying,
|
|
_default_summary,
|
|
_has_any_time,
|
|
_passes_default_confidence,
|
|
_sum_positive,
|
|
_worst_hours,
|
|
)
|
|
from steam_backlog_enforcer.steam_api import GameInfo
|
|
|
|
_PKG = "steam_backlog_enforcer._web_dataset"
|
|
|
|
|
|
def _gi(**over: object) -> GameInfo:
|
|
"""Build a GameInfo with field overrides."""
|
|
base = GameInfo(
|
|
app_id=1,
|
|
name="G",
|
|
total_achievements=10,
|
|
unlocked_achievements=0,
|
|
playtime_minutes=60,
|
|
completionist_hours=20.0,
|
|
comp_100_count=5,
|
|
count_comp=20,
|
|
)
|
|
return replace(base, **over)
|
|
|
|
|
|
def _wg(**over: object) -> WebGame:
|
|
"""Build a WebGame with field overrides."""
|
|
base = WebGame(
|
|
app_id=1,
|
|
name="Game1",
|
|
completion_pct=0.0,
|
|
playtime_minutes=60,
|
|
rush_hours=10.0,
|
|
leisure_hours=20.0,
|
|
worst_hours=25.0,
|
|
count_comp=20,
|
|
comp_100_count=5,
|
|
hltb_game_id=0,
|
|
protondb_tier="gold",
|
|
protondb_trending_tier="gold",
|
|
protondb_score=0.8,
|
|
)
|
|
return replace(base, **over)
|
|
|
|
|
|
def _complete_game(
|
|
app_id: int = 1,
|
|
playtime_minutes: int = 600,
|
|
) -> GameInfo:
|
|
"""Complete game (100 % achievements, has playtime)."""
|
|
return GameInfo(
|
|
app_id=app_id,
|
|
name=f"Done{app_id}",
|
|
total_achievements=10,
|
|
unlocked_achievements=10,
|
|
playtime_minutes=playtime_minutes,
|
|
completionist_hours=0.0,
|
|
comp_100_count=5,
|
|
count_comp=20,
|
|
)
|
|
|
|
|
|
class TestWorstHours:
|
|
"""Tests for _worst_hours (mirrors _stats worst-case selection)."""
|
|
|
|
def test_completionist_dominates(self) -> None:
|
|
"""Test completionist dominates."""
|
|
game = _gi(completionist_hours=30.0)
|
|
assert _worst_hours(game, cache_hours=10.0, leisure=20.0) == 30.0
|
|
|
|
def test_falls_back_to_cache_and_leisure_when_completionist_zero(self) -> None:
|
|
"""Test falls back to cache and leisure when completionist zero."""
|
|
game = _gi(completionist_hours=0.0)
|
|
assert _worst_hours(game, cache_hours=15.0, leisure=8.0) == 15.0
|
|
|
|
def test_minus_one_when_all_non_positive(self) -> None:
|
|
"""Test minus one when all non positive."""
|
|
game = _gi(completionist_hours=0.0)
|
|
assert _worst_hours(game, cache_hours=-1.0, leisure=-1.0) == -1.0
|
|
|
|
|
|
class TestPassesDefaultConfidence:
|
|
"""Tests for _passes_default_confidence."""
|
|
|
|
def test_fail_low_comp_100(self) -> None:
|
|
"""Test fail low comp 100."""
|
|
assert _passes_default_confidence(_wg(comp_100_count=2)) is False
|
|
|
|
def test_fail_low_count_comp(self) -> None:
|
|
"""Test fail low count comp."""
|
|
assert _passes_default_confidence(_wg(comp_100_count=5, count_comp=10)) is False
|
|
|
|
def test_pass_when_all_thresholds_met(self) -> None:
|
|
"""Test pass when all thresholds met."""
|
|
assert _passes_default_confidence(_wg(comp_100_count=5, count_comp=20)) is True
|
|
|
|
|
|
class TestHasAnyTime:
|
|
"""Tests for _has_any_time."""
|
|
|
|
def test_true_when_some_positive(self) -> None:
|
|
"""Test true when some positive."""
|
|
assert _has_any_time(_wg(rush_hours=-1, leisure_hours=-1, worst_hours=5.0))
|
|
|
|
def test_false_when_all_non_positive(self) -> None:
|
|
"""Test false when all non positive."""
|
|
game = _wg(rush_hours=-1, leisure_hours=-1, worst_hours=-1)
|
|
assert _has_any_time(game) is False
|
|
|
|
|
|
class TestDefaultQualifying:
|
|
"""Tests for _default_qualifying — each filter rejection branch."""
|
|
|
|
def test_rejects_low_confidence(self) -> None:
|
|
"""Test rejects low confidence."""
|
|
assert _default_qualifying([_wg(count_comp=0)]) == []
|
|
|
|
def test_rejects_unplayable(self) -> None:
|
|
"""Test rejects unplayable."""
|
|
game = _wg(protondb_tier="borked", protondb_trending_tier="borked")
|
|
assert _default_qualifying([game]) == []
|
|
|
|
def test_rejects_no_time(self) -> None:
|
|
"""Test rejects no time."""
|
|
game = _wg(rush_hours=-1, leisure_hours=-1, worst_hours=-1)
|
|
assert _default_qualifying([game]) == []
|
|
|
|
def test_accepts_qualifying_game(self) -> None:
|
|
"""Test accepts qualifying game."""
|
|
assert len(_default_qualifying([_wg()])) == 1
|
|
|
|
|
|
class TestSumPositive:
|
|
"""Tests for _sum_positive."""
|
|
|
|
def test_sums_only_positive(self) -> None:
|
|
"""Test sums only positive."""
|
|
rows = [_wg(rush_hours=10.0), _wg(rush_hours=-1.0), _wg(rush_hours=5.5)]
|
|
assert _sum_positive(rows, "rush_hours") == 15.5
|
|
|
|
def test_empty(self) -> None:
|
|
"""Test empty."""
|
|
assert _sum_positive([], "rush_hours") == 0.0
|
|
|
|
|
|
class TestDefaultSummary:
|
|
"""Tests for _default_summary."""
|
|
|
|
def test_totals(self) -> None:
|
|
"""Test totals."""
|
|
rows = [_wg(rush_hours=10.0, leisure_hours=20.0, worst_hours=25.0)]
|
|
summary = _default_summary(rows)
|
|
assert summary.qualifying == 1
|
|
assert summary.rush_total == 10.0
|
|
assert summary.leisure_total == 20.0
|
|
assert summary.worst_total == 25.0
|