166 lines
7.0 KiB
Python
166 lines
7.0 KiB
Python
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()
|