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()