Files
MHW-Mod-Manager/tests/test_library.py
T

168 lines
7.1 KiB
Python

import unittest
from mhwmm.core import archive, layout
from mhwmm.core.library import Library, LibraryError
from mhwmm.core.manager import Manager
from mhwmm.core.config import Settings
from mhwmm.core.modinfo import parse_modinfo
from mhwmm.core.profiles import Profiles
from tests.helpers import TmpCase, make_game, make_zip, write_tree
import zipfile
class ArchiveTests(TmpCase):
def test_zip_slip_rejected(self):
z = make_zip(self.tmp / "evil.zip", {"../../evil.txt": "x"})
with self.assertRaises(archive.ArchiveError):
archive.extract(z, self.tmp / "out")
self.assertFalse((self.tmp / "evil.txt").exists())
def test_absolute_and_drive_paths_rejected(self):
for name in ("/etc/evil", "C:/evil", "a/../../evil", "..\\evil"):
z = make_zip(self.tmp / "e.zip", {name: "x"})
with self.assertRaises(archive.ArchiveError, msg=name):
archive.extract(z, self.tmp / "out2")
def test_symlink_entry_rejected(self):
p = self.tmp / "s.zip"
with zipfile.ZipFile(p, "w") as zf:
info = zipfile.ZipInfo("link")
info.external_attr = 0o120777 << 16
zf.writestr(info, "/etc/passwd")
with self.assertRaises(archive.ArchiveError):
archive.extract(p, self.tmp / "o")
def test_sniff_ignores_extension(self):
z = make_zip(self.tmp / "actually.rar", {"a": "b"})
self.assertEqual(archive.sniff(z), "zip")
(self.tmp / "junk.zip").write_bytes(b"nope")
with self.assertRaises(archive.ArchiveError):
archive.sniff(self.tmp / "junk.zip")
def test_good_zip_extracts(self):
z = make_zip(self.tmp / "ok.zip", {"natives/STM/a.bin": "A"})
archive.extract(z, self.tmp / "ok")
self.assertEqual((self.tmp / "ok/natives/STM/a.bin").read_text(), "A")
class LayoutTests(TmpCase):
def classify(self, files):
return layout.classify(write_tree(self.tmp / f"t{len(list(self.tmp.iterdir()))}", files))
def dests(self, c):
return sorted(e.dest for e in c.entries)
def test_natives_any_depth_case_insensitive(self):
c = self.classify({"Wrapper/Inner/Natives/STM/x.bin": "1", "Wrapper/Inner/readme.txt": "r"})
self.assertEqual(self.dests(c), ["natives/STM/x.bin"])
def test_bare_stm(self):
self.assertEqual(self.dests(self.classify({"STM/a/b.bin": "1"})), ["natives/STM/a/b.bin"])
def test_reframework_preserved(self):
c = self.classify({"reframework/autorun/a.lua": "1", "reframework/plugins/p.dll": "2",
"reframework/data/keep.json": "3"})
self.assertEqual(self.dests(c), ["reframework/autorun/a.lua", "reframework/plugins/p.dll"])
self.assertTrue(c.notes)
def test_pak_near_root(self):
c = self.classify({"paks/re_chunk_000.pak.sub_000.pak.patch_020.pak": "1", "x.pak": "2"})
self.assertEqual(sorted((e.kind, e.chain) for e in c.entries), [("pak", "main"), ("pak", "sub")])
def test_loose_lua_and_ambiguous_dll(self):
c = self.classify({"script.lua": "1", "mystery.dll": "2"})
self.assertEqual(self.dests(c), ["reframework/autorun/script.lua"])
self.assertEqual(len(c.ambiguous), 1)
def test_mixed_kinds_in_one_mod(self):
c = self.classify({"natives/STM/a.bin": "1", "reframework/autorun/a.lua": "2", "m.pak": "3"})
self.assertEqual({e.kind for e in c.entries}, {"loose", "reframework", "pak"})
def test_junk_only_is_empty(self):
self.assertTrue(self.classify({"readme.txt": "hi", "modinfo.ini": "name=x"}).empty)
class LibraryTests(TmpCase):
def test_import_reads_modinfo_and_screenshot(self):
z = make_zip(self.tmp / "Cool Mod-123-1-0-99.zip", {
"modinfo.ini": "name=Cool Mod\nversion=1.2\nauthor=me\ncategory=Armor\ndescription=hello",
"screenshot.png": b"\x89PNG",
"natives/STM/a.bin": "A"})
lib = Library(self.paths)
mod = lib.import_source(z)
self.assertEqual((mod.name, mod.version, mod.category), ("Cool Mod", "1.2", "Armor"))
self.assertTrue(mod.screenshot_path and mod.screenshot_path.name == "screenshot.png")
lib2 = Library(self.paths)
lib2.scan()
self.assertIn(mod.id, lib2.mods)
self.assertEqual(lib2.categories(), ["Armor"])
def test_import_rejects_empty_and_leaves_no_debris(self):
z = make_zip(self.tmp / "junk.zip", {"readme.txt": "x"})
lib = Library(self.paths)
with self.assertRaises(LibraryError):
lib.import_source(z)
self.assertEqual([p for p in self.paths.mods_dir.iterdir()], [])
def test_duplicate_names_get_unique_ids(self):
lib = Library(self.paths)
ids = {lib.import_source(make_zip(self.tmp / f"m{i}.zip", {"modinfo.ini": "name=Same", "STM/a": "x"})).id
for i in range(3)}
self.assertEqual(len(ids), 3)
def test_modinfo_tolerant(self):
i = parse_modinfo("\ufeff[~ modinfo ~]\nName = X \n; c\nVersion=2\n")
self.assertEqual((i.name, i.version), ("X", "2"))
class ProfileTests(TmpCase):
def test_profiles_roundtrip(self):
p = Profiles(self.paths.profiles_file)
p.reconcile(["a", "b", "c"])
p.set_enabled("a", True)
p.set_enabled("c", True)
p.move("c", 0)
p.create("Alt", copy_from="Default")
p.switch("Alt")
p.set_enabled("b", True)
p.save()
q = Profiles(self.paths.profiles_file)
self.assertEqual(q.active, "Alt")
self.assertEqual(q.data["Default"]["enabled"], ["a", "c"])
q.switch("Default")
self.assertEqual(q.enabled_in_order(), ["c", "a"])
q.reconcile(["a", "b"]) # c vanished from the library
self.assertEqual(q.enabled_in_order(), ["a"])
class ManagerTests(TmpCase):
def test_end_to_end_and_startup_reslot(self):
game = make_game(self.tmp)
s = Settings(game_dir=str(game))
m = Manager(self.paths, s)
self.assertTrue(m.startup().game_found)
self.assertEqual(m.pak_summary()["main"]["ceiling"], 15)
mod = m.import_source(make_zip(self.tmp / "a.zip", {"modinfo.ini": "name=A", "natives/STM/a.bin": "A", "a.pak": "P"}))
m.set_enabled(mod.id, True)
self.assertTrue((game / "natives/STM/a.bin").is_symlink())
self.assertEqual(m.pak_summary()["main"]["mod_slots"], [16])
self.assertFalse(m.pending())
# Steam patches while the manager is closed
(game / "re_chunk_000.pak.patch_016.pak").unlink()
(game / "re_chunk_000.pak.patch_016.pak").write_bytes(b"steam")
m2 = Manager(self.paths, Settings(game_dir=str(game)))
rep = m2.startup()
self.assertTrue(any("re-slotted to 017+" in n for n in rep.notes), rep.notes)
self.assertEqual((game / "re_chunk_000.pak.patch_016.pak").read_bytes(), b"steam")
self.assertEqual(m2.pak_summary()["main"]["mod_slots"], [17])
m2.set_enabled(mod.id, False)
self.assertFalse((game / "natives").exists())
m2.delete_mod(mod.id)
self.assertEqual(m2.library.mods, {})
if __name__ == "__main__":
unittest.main()