Initial commit with nexus updates and streamlined UI
This commit is contained in:
commit
f780daecad
41 files changed
+4668
No files matched your search
@@ -0,0 +1,165 @@
|
||||
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()
|
||||
Reference in new issue
Block a user