fix: match mod folder by steam_id when folder name diverges from modlist name
_find_folder in mods.py now has a fourth fallback: reads publishedid from meta.cpp inside each candidate folder and matches against mod["steam_id"]. Fixes mods appearing as "not downloaded" when the folder name on disk differs from the name in the modlist but the mod content (meta.cpp) is correct. Also adds 8 tests covering all four match strategies and edge cases.
This commit is contained in:
122
test_suite.py
122
test_suite.py
@@ -2229,6 +2229,128 @@ test("live: list_mod_files entries are (rel_path, url, size) tuples", _test_liv
|
||||
test("live: find_mod_folder name fallback works (no steam_id)", _test_live_find_mod_by_name_fallback)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# gui.views.mods — _find_folder
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
group("gui.views.mods — _find_folder")
|
||||
|
||||
import importlib.util as _mods_ilu
|
||||
|
||||
_mods_spec = _mods_ilu.spec_from_file_location(
|
||||
"gui.views.mods", Path(__file__).parent / "gui" / "views" / "mods.py"
|
||||
)
|
||||
_mods_mod = _mods_ilu.module_from_spec(_mods_spec)
|
||||
# Stub out customtkinter and gui imports so the module loads without a display
|
||||
import types as _types
|
||||
import sys as _sys
|
||||
|
||||
for _stub in ("customtkinter", "gui", "gui._constants", "gui.locales",
|
||||
"gui.views", "gui.views.base"):
|
||||
if _stub not in _sys.modules:
|
||||
_sys.modules[_stub] = _types.ModuleType(_stub)
|
||||
|
||||
# Provide the colour constants the module references at import time
|
||||
_sys.modules["gui._constants"].COLOR_OK = "#4CAF50"
|
||||
_sys.modules["gui._constants"].COLOR_ERROR = "#F44336"
|
||||
_sys.modules["gui._constants"].COLOR_WARN = "#FF9800"
|
||||
_sys.modules["gui._constants"].COLOR_RUNNING = "#2196F3"
|
||||
|
||||
# Stub BaseView so the class body does not fail
|
||||
_base_stub = _sys.modules["gui.views.base"] = _types.ModuleType("gui.views.base")
|
||||
_base_stub.BaseView = object
|
||||
|
||||
# Stub locales.t so string calls don't crash
|
||||
_sys.modules["gui.locales"].t = lambda key, **kw: key
|
||||
|
||||
_mods_spec.loader.exec_module(_mods_mod)
|
||||
_find_folder_fn = _mods_mod._find_folder
|
||||
|
||||
|
||||
def _test_ff_returns_none_for_missing_group(tmp_path):
|
||||
assert _find_folder_fn(tmp_path / "nonexistent", "MyMod") is None
|
||||
|
||||
|
||||
def _test_ff_exact_match(tmp_path):
|
||||
(tmp_path / "@MyMod").mkdir()
|
||||
result = _find_folder_fn(tmp_path, "MyMod")
|
||||
assert result == tmp_path / "@MyMod"
|
||||
|
||||
|
||||
def _test_ff_case_insensitive_match(tmp_path):
|
||||
(tmp_path / "@mymod").mkdir()
|
||||
result = _find_folder_fn(tmp_path, "MyMod")
|
||||
assert result == tmp_path / "@mymod"
|
||||
|
||||
|
||||
def _test_ff_normalized_match(tmp_path):
|
||||
# Folder on disk uses underscores; modlist name uses spaces — both normalize to same string
|
||||
(tmp_path / "@My_Mod_Edition").mkdir()
|
||||
result = _find_folder_fn(tmp_path, "My Mod Edition")
|
||||
assert result == tmp_path / "@My_Mod_Edition"
|
||||
|
||||
|
||||
def _test_ff_steam_id_fallback(tmp_path):
|
||||
"""Folder name bears no resemblance to mod name but meta.cpp has correct ID."""
|
||||
folder = tmp_path / "@ServerCanonicalName"
|
||||
folder.mkdir()
|
||||
(folder / "meta.cpp").write_text('publishedid = 123456789;\nname = "Some Mod";\n')
|
||||
result = _find_folder_fn(tmp_path, "Completely Different Name", steam_id="123456789")
|
||||
assert result == folder
|
||||
|
||||
|
||||
def _test_ff_steam_id_no_false_positive(tmp_path):
|
||||
"""Wrong steam_id in meta.cpp must not match."""
|
||||
folder = tmp_path / "@WrongMod"
|
||||
folder.mkdir()
|
||||
(folder / "meta.cpp").write_text('publishedid = 999999999;\n')
|
||||
result = _find_folder_fn(tmp_path, "My Mod", steam_id="123456789")
|
||||
assert result is None
|
||||
|
||||
|
||||
def _test_ff_steam_id_skipped_when_none(tmp_path):
|
||||
"""No steam_id supplied → meta.cpp is never read (no false positives)."""
|
||||
folder = tmp_path / "@SomeFolder"
|
||||
folder.mkdir()
|
||||
(folder / "meta.cpp").write_text('publishedid = 123456789;\n')
|
||||
result = _find_folder_fn(tmp_path, "My Mod", steam_id=None)
|
||||
assert result is None
|
||||
|
||||
|
||||
def _test_ff_missing_meta_cpp_skipped(tmp_path):
|
||||
"""Folders without meta.cpp are silently skipped in the steam_id pass."""
|
||||
folder = tmp_path / "@NoMeta"
|
||||
folder.mkdir()
|
||||
result = _find_folder_fn(tmp_path, "My Mod", steam_id="123456789")
|
||||
assert result is None
|
||||
|
||||
|
||||
# Wrap tmp_path calls in lambdas that supply a temp dir
|
||||
def _with_tmp(fn):
|
||||
def wrapper():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
fn(Path(d))
|
||||
return wrapper
|
||||
|
||||
|
||||
test("_find_folder: None when group dir missing",
|
||||
_with_tmp(_test_ff_returns_none_for_missing_group))
|
||||
test("_find_folder: exact @ModName match",
|
||||
_with_tmp(_test_ff_exact_match))
|
||||
test("_find_folder: case-insensitive name match",
|
||||
_with_tmp(_test_ff_case_insensitive_match))
|
||||
test("_find_folder: normalized name match (punctuation differs)",
|
||||
_with_tmp(_test_ff_normalized_match))
|
||||
test("_find_folder: steam_id fallback via meta.cpp",
|
||||
_with_tmp(_test_ff_steam_id_fallback))
|
||||
test("_find_folder: wrong steam_id in meta.cpp is not a match",
|
||||
_with_tmp(_test_ff_steam_id_no_false_positive))
|
||||
test("_find_folder: no steam_id supplied → meta.cpp not checked",
|
||||
_with_tmp(_test_ff_steam_id_skipped_when_none))
|
||||
test("_find_folder: missing meta.cpp silently skipped",
|
||||
_with_tmp(_test_ff_missing_meta_cpp_skipped))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Summary
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user