import io import json import unittest import zipfile from pathlib import Path from mhwmm.core import loader, nexus, steam from tests.helpers import TmpCase, make_zip VDF = '''"libraryfolders" { \t"0" \t{ \t\t"path"\t\t"/tmp/a b" \t\t"apps" { "2246340" "1" } \t} \t"1" { "path" "/mnt/games" } } ''' class VDFTests(unittest.TestCase): def test_parse_and_case_insensitive_get(self): d = steam.parse_vdf(VDF) self.assertEqual(steam.vdf_get(d, "LibraryFolders", "1", "PATH"), "/mnt/games") def test_roundtrip_dump(self): d = steam.parse_vdf(VDF) self.assertEqual(steam.parse_vdf(steam.dump_vdf(d)), d) def test_escapes_and_comments(self): d = steam.parse_vdf('// hi\n"a" "x\\"y\\\\z"\n') self.assertEqual(d["a"], 'x"y\\z') def test_errors(self): for bad in ('"a" {', '}', '{ "a" "b" }'): with self.assertRaises(steam.VDFError): steam.parse_vdf(bad) def test_real_files_on_this_machine(self): root = Path.home() / ".steam/steam/steamapps" if not (root / "appmanifest_2246340.acf").is_file(): self.skipTest("Monster Hunter Wilds not installed here") acf = steam.vdf_get(steam.read_vdf(root / "appmanifest_2246340.acf"), "AppState") self.assertEqual(acf["appid"], "2246340") self.assertTrue(acf["installdir"]) self.assertTrue(steam.library_folders(root.parent)) found = steam.find_app() self.assertIsNotNone(found) self.assertTrue((found.game_dir / "MonsterHunterWilds.exe").exists()) def test_launch_options(self): req = steam.REQUIRED_LAUNCH_OPTIONS self.assertTrue(steam.launch_options_ok(req)) self.assertFalse(steam.launch_options_ok("")) self.assertFalse(steam.launch_options_ok("mangohud %command%")) self.assertEqual(steam.suggest_launch_options(""), req) self.assertEqual(steam.suggest_launch_options("mangohud %command%"), 'WINEDLLOVERRIDES="dinput8.dll=n,b" mangohud %command%') self.assertEqual(steam.suggest_launch_options('WINEDLLOVERRIDES="winmm=n" %command%'), 'WINEDLLOVERRIDES="winmm=n;dinput8.dll=n,b" %command%') self.assertEqual(steam.suggest_launch_options(req), req) class LocalConfigTests(TmpCase): def test_read_and_write_launch_options(self): lc = self.tmp / "localconfig.vdf" lc.write_text('"UserLocalConfigStore"\n{\n\t"Software"\n\t{\n\t\t"Valve"\n\t\t{\n\t\t\t"Steam"\n\t\t\t{\n' '\t\t\t\t"apps"\n\t\t\t\t{\n\t\t\t\t\t"2246340"\n\t\t\t\t\t{\n\t\t\t\t\t\t"LaunchOptions"\t\t"-x"\n' '\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n') self.assertEqual(steam.read_launch_options(lc), "-x") orig = steam.steam_running try: steam.steam_running = lambda: True with self.assertRaises(steam.SteamRunningError): steam.write_launch_options(lc, "y") self.assertEqual(steam.read_launch_options(lc), "-x") steam.steam_running = lambda: False bak = steam.write_launch_options(lc, steam.REQUIRED_LAUNCH_OPTIONS) finally: steam.steam_running = orig self.assertTrue(bak.exists()) self.assertEqual(steam.read_launch_options(lc), steam.REQUIRED_LAUNCH_OPTIONS) class LoaderTests(TmpCase): def test_config_patch_preserves_other_keys(self): cfg = self.tmp / loader.CONFIG_NAME cfg.write_text("Foo=1\nlooseFileLoader_enabled=false\nBar=2\n") loader.enable_loose_loader(self.tmp) self.assertEqual(cfg.read_text(), "Foo=1\nlooseFileLoader_enabled=true\nBar=2\n") def test_config_created_when_absent(self): loader.enable_loose_loader(self.tmp) self.assertTrue(loader.loose_loader_enabled(self.tmp)) def test_status_checks(self): st = loader.status(self.tmp, None) self.assertEqual([c.ok for c in st.checks], [False, False, False]) (self.tmp / "dinput8.dll").write_bytes(b"x") loader.enable_loose_loader(self.tmp) st = loader.status(self.tmp, steam.REQUIRED_LAUNCH_OPTIONS) self.assertTrue(st.all_ok) def test_install_from_release(self): blob = io.BytesIO() with zipfile.ZipFile(blob, "w") as zf: zf.writestr("dinput8.dll", b"MZfake") zf.writestr("reframework/x", b"ignored") rel = json.dumps({"tag_name": "nightly-1", "assets": [ {"name": "MHWILDS.zip", "browser_download_url": "https://example/MHWILDS.zip"}]}).encode() fetch = lambda url: rel if url == loader.RELEASES_API else blob.getvalue() self.assertEqual(loader.install_reframework(self.tmp, fetch), "nightly-1") self.assertEqual((self.tmp / "dinput8.dll").read_bytes(), b"MZfake") self.assertFalse((self.tmp / "reframework").exists()) class NexusTests(TmpCase): def test_parse_nxm_with_key_and_expires(self): l = nexus.parse_nxm("nxm://monsterhunterwilds/mods/123/files/456?key=AbC_-9&expires=1700000000&user_id=1") self.assertEqual((l.game, l.mod_id, l.file_id, l.key, l.expires), ("monsterhunterwilds", 123, 456, "AbC_-9", "1700000000")) def test_parse_rejects_garbage(self): for bad in ("https://x/mods/1/files/2", "nxm://g/mods/x/files/2", "nxm:///mods/1/files/2", "nxm://g/mods/1"): with self.assertRaises(nexus.NexusError, msg=bad): nexus.parse_nxm(bad) def test_client_passes_key_and_records_limits(self): seen = {} class Resp: headers = {"x-rl-daily-remaining": "99", "x-rl-daily-limit": "100", "x-rl-hourly-remaining": "9", "x-rl-hourly-limit": "10"} def read(self): return b'[{"URI": "https://cdn/x.zip"}]' def opener(req): seen["url"], seen["headers"] = req.full_url, dict(req.header_items()) return Resp() c = nexus.NexusClient("SECRET", opener) out = c.download_links(nexus.parse_nxm("nxm://monsterhunterwilds/mods/1/files/2?key=K&expires=5")) self.assertEqual(out[0]["URI"], "https://cdn/x.zip") self.assertIn("/v1/games/monsterhunterwilds/mods/1/files/2/download_link.json?key=K&expires=5", seen["url"]) self.assertEqual(seen["headers"]["Apikey"], "SECRET") self.assertIn("9/10 hourly", c.limits.summary()) def test_no_key_is_a_clear_error(self): with self.assertRaises(nexus.NexusError): nexus.NexusClient("").mod_info(nexus.parse_nxm("nxm://monsterhunterwilds/mods/1/files/2")) def test_keystore_file_fallback_is_0600(self): ks = nexus.KeyStore(self.tmp / "secrets.json") nexus._secret_mod = lambda: None ks.set("abc") self.assertEqual(ks.get(), "abc") self.assertEqual((self.tmp / "secrets.json").stat().st_mode & 0o777, 0o600) def test_download_refuses_http(self): with self.assertRaises(nexus.NexusError): nexus.download("http://x/y.zip", self.tmp) if __name__ == "__main__": unittest.main()