"""Tests for M6 Steam library & game detection (VDF parse, scan, tool filter, cache diff).""" import tempfile import unittest from pathlib import Path from unittest import mock from rigdoctor.core import steam _GAME_ACF = """"AppState" {{ \t"appid"\t\t"{appid}" \t"name"\t\t"{name}" \t"installdir"\t\t"{installdir}" \t"SizeOnDisk"\t\t"{size}" \t"LastUpdated"\t\t"{updated}" }} """ _LIBRARYFOLDERS = """"libraryfolders" {{ \t"0" \t{{ \t\t"path"\t\t"{path}" \t\t"label"\t\t"Main" \t\t"apps" \t\t{{ \t\t\t"570"\t\t"123" \t\t}} \t}} }} """ def _make_library(root: Path, games) -> Path: """games: list of (appid, name, installdir, size, updated). Returns the library path.""" steamapps = root / "steamapps" steamapps.mkdir(parents=True, exist_ok=True) for appid, name, installdir, size, updated in games: (steamapps / f"appmanifest_{appid}.acf").write_text( _GAME_ACF.format(appid=appid, name=name, installdir=installdir, size=size, updated=updated) ) return root class VdfTests(unittest.TestCase): def test_parse_nested_and_pairs(self): data = steam._parse_vdf(_GAME_ACF.format( appid="570", name="Dota 2", installdir="dota 2 beta", size="15", updated="1700")) state = data["AppState"] self.assertEqual(state["appid"], "570") self.assertEqual(state["name"], "Dota 2") self.assertEqual(state["installdir"], "dota 2 beta") def test_parse_handles_quotes_in_names(self): acf = _GAME_ACF.format(appid="1", name="Baldur\\'s Gate 3", installdir="bg3", size="1", updated="1") data = steam._parse_vdf(acf) self.assertIn("Baldur", data["AppState"]["name"]) def test_parse_garbage_returns_empty(self): self.assertEqual(steam._parse_vdf("not vdf at all"), {}) class ToolFilterTests(unittest.TestCase): def test_known_tool_appid(self): self.assertTrue(steam.is_tool("228980", "Steamworks Common Redistributables")) def test_proton_name_prefix(self): self.assertTrue(steam.is_tool("9999999", "Proton 8.0")) self.assertTrue(steam.is_tool("9999998", "Steam Linux Runtime 3.0 (sniper)")) def test_real_game_is_not_a_tool(self): self.assertFalse(steam.is_tool("570", "Dota 2")) class ScanTests(unittest.TestCase): def test_scan_library_filters_tools(self): with tempfile.TemporaryDirectory() as d: lib = _make_library(Path(d), [ ("570", "Dota 2", "dota 2 beta", "15000000000", "1700000000"), ("228980", "Steamworks Common Redistributables", "Steamworks Shared", "0", "0"), ("1493710", "Proton Experimental", "Proton - Experimental", "0", "0"), ]) games = steam.scan_library(str(lib)) names = {g.name for g in games} self.assertEqual(names, {"Dota 2"}) self.assertEqual(games[0].size_bytes, 15000000000) def test_scan_games_dedupes_and_sorts(self): with tempfile.TemporaryDirectory() as d1, tempfile.TemporaryDirectory() as d2: a = _make_library(Path(d1), [("10", "Zeta", "zeta", "1", "1"), ("20", "Alpha", "alpha", "1", "1")]) b = _make_library(Path(d2), [("20", "Alpha", "alpha", "1", "1")]) # dup appid 20 games = steam.scan_games([str(a), str(b)]) self.assertEqual([g.name for g in games], ["Alpha", "Zeta"]) # sorted, deduped class DiscoverTests(unittest.TestCase): def test_discover_reads_libraryfolders(self): with tempfile.TemporaryDirectory() as d: root = Path(d) / "Steam" (root / "steamapps").mkdir(parents=True) extra = Path(d) / "Extra" (extra / "steamapps").mkdir(parents=True) (root / "steamapps" / "libraryfolders.vdf").write_text( _LIBRARYFOLDERS.format(path=str(extra))) with mock.patch.object(steam, "steam_roots", return_value=[root]): libs = steam.discover_libraries() paths = {lib.path for lib in libs} self.assertIn(str(root.resolve()), paths) # root itself self.assertIn(str(extra.resolve()), paths) # the configured extra library class CacheDiffTests(unittest.TestCase): def _rescan(self, lib, games_file, cfg): with mock.patch.object(steam, "GAMES_FILE", games_file): return steam.rescan(cfg=cfg) def test_first_scan_has_no_new_then_added_game_is_new(self): with tempfile.TemporaryDirectory() as d: lib = _make_library(Path(d) / "lib", [("10", "Alpha", "alpha", "1", "1")]) games_file = Path(d) / "games.json" cfg = {"steam_libraries": [str(lib)]} first = self._rescan(lib, games_file, cfg) self.assertEqual(first.new_appids, []) # first run flags nothing as new # Install a second game; it should be flagged new on the next scan. _make_library(lib, [("10", "Alpha", "alpha", "1", "1"), ("20", "Beta", "beta", "1", "1")]) second = self._rescan(lib, games_file, cfg) self.assertEqual(second.new_appids, ["20"]) self.assertEqual({g.name for g in second.games}, {"Alpha", "Beta"}) def test_acknowledge_clears_new(self): with tempfile.TemporaryDirectory() as d: lib = _make_library(Path(d) / "lib", [("10", "Alpha", "alpha", "1", "1")]) games_file = Path(d) / "games.json" cfg = {"steam_libraries": [str(lib)]} self._rescan(lib, games_file, cfg) _make_library(lib, [("10", "Alpha", "alpha", "1", "1"), ("20", "Beta", "beta", "1", "1")]) self._rescan(lib, games_file, cfg) with mock.patch.object(steam, "GAMES_FILE", games_file): steam.acknowledge_new() self.assertEqual(steam.load_cache()["new_appids"], []) if __name__ == "__main__": unittest.main()