feat: add migrate step to move mod folders between groups on preset change
Before step_fetch, scan all downloads/ subdirs and move any mod that comparison.json now assigns to a different group. Matching uses steam_id (via meta.cpp publishedid) first, normalized name as fallback. Stale junctions in arma_dir are removed before the folder move so step_link can re-create them pointing to the new location. - New arma_modlist_tools/migrator.py: migrate_mod_groups() - run.py: step_migrate(), --skip-migrate flag, wired into dispatch loop - gui/app.py: step_migrate inserted as Step 3/5 between compare and fetch - gui/locales.py: add step3/4/5 names (en + vi), renumber old 3->4, 4->5 - test_suite.py: 7 new migrator tests (158 total, 0 failed)
This commit is contained in:
112
test_suite.py
112
test_suite.py
@@ -2351,6 +2351,118 @@ test("_find_folder: missing meta.cpp silently skipped",
|
||||
_with_tmp(_test_ff_missing_meta_cpp_skipped))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# migrator — migrate_mod_groups
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
group("migrator — migrate_mod_groups")
|
||||
|
||||
from arma_modlist_tools.migrator import migrate_mod_groups as _migrate
|
||||
|
||||
|
||||
def _make_mod(dl: Path, grp: str, folder: str, steam_id: str | None = None) -> Path:
|
||||
"""Create a minimal mod folder under downloads/group/folder."""
|
||||
d = dl / grp / folder
|
||||
d.mkdir(parents=True)
|
||||
if steam_id:
|
||||
(d / "meta.cpp").write_text(f"publishedid = {steam_id};\n", encoding="utf-8")
|
||||
(d / "dummy.pbo").write_bytes(b"\x00" * 8)
|
||||
return d
|
||||
|
||||
|
||||
def _test_migrate_already_correct():
|
||||
comp = {"shared": {"mods": [{"name": "CBA_A3", "steam_id": "450814997"}]}, "unique": {}}
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
dl = Path(d) / "downloads"
|
||||
_make_mod(dl, "shared", "@CBA_A3", "450814997")
|
||||
result = _migrate(dl, None, comp)
|
||||
assert_eq(result["moved"], 0)
|
||||
assert_eq(result["skipped_correct"], 1)
|
||||
|
||||
|
||||
def _test_migrate_moves_by_steam_id():
|
||||
comp = {"shared": {"mods": []}, "unique": {
|
||||
"A_v1": {"mods": [{"name": "ACE3", "steam_id": "463939057"}]}
|
||||
}}
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
dl = Path(d) / "downloads"
|
||||
old = _make_mod(dl, "A", "@ACE3", "463939057")
|
||||
result = _migrate(dl, None, comp)
|
||||
assert_eq(result["moved"], 1)
|
||||
assert not old.exists(), "old folder must be gone"
|
||||
assert (dl / "A_v1" / "@ACE3").exists(), "new folder must exist"
|
||||
assert (dl / "A_v1" / "@ACE3" / "dummy.pbo").exists(), "files preserved"
|
||||
|
||||
|
||||
def _test_migrate_moves_by_normalized_name():
|
||||
"""No meta.cpp — matching falls back to normalised folder name."""
|
||||
comp = {"shared": {"mods": [{"name": "CBA_A3", "steam_id": None}]}, "unique": {
|
||||
"A": {"mods": []}
|
||||
}}
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
dl = Path(d) / "downloads"
|
||||
_make_mod(dl, "A", "@CBA_A3", steam_id=None)
|
||||
result = _migrate(dl, None, comp)
|
||||
assert_eq(result["moved"], 1)
|
||||
assert (dl / "shared" / "@CBA_A3").exists()
|
||||
|
||||
|
||||
def _test_migrate_skips_dest_exists():
|
||||
comp = {"shared": {"mods": [{"name": "CBA_A3", "steam_id": "450814997"}]}, "unique": {}}
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
dl = Path(d) / "downloads"
|
||||
_make_mod(dl, "A", "@CBA_A3", "450814997")
|
||||
_make_mod(dl, "shared", "@CBA_A3", "450814997")
|
||||
result = _migrate(dl, None, comp)
|
||||
assert_eq(result["moved"], 0)
|
||||
assert_eq(result["skipped_dest_exists"], 1)
|
||||
|
||||
|
||||
def _test_migrate_skips_not_on_disk():
|
||||
comp = {"shared": {"mods": [{"name": "CBA_A3", "steam_id": "450814997"}]}, "unique": {}}
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
dl = Path(d) / "downloads"
|
||||
dl.mkdir()
|
||||
result = _migrate(dl, None, comp)
|
||||
assert_eq(result["skipped_not_found"], 1)
|
||||
assert_eq(result["moved"], 0)
|
||||
|
||||
|
||||
def _test_migrate_removes_stale_junction():
|
||||
from arma_modlist_tools.linker import create_junction, _is_junction
|
||||
comp = {"shared": {"mods": [{"name": "ACE3", "steam_id": "463939057"}]}, "unique": {}}
|
||||
with tempfile.TemporaryDirectory() as dl_d, \
|
||||
tempfile.TemporaryDirectory() as arma_d:
|
||||
dl = Path(dl_d) / "downloads"
|
||||
arma = Path(arma_d)
|
||||
old = _make_mod(dl, "A", "@ACE3", "463939057")
|
||||
link = arma / "@ACE3"
|
||||
create_junction(link, old)
|
||||
assert _is_junction(link), "precondition: junction must exist"
|
||||
result = _migrate(dl, arma, comp)
|
||||
assert_eq(result["moved"], 1)
|
||||
assert_eq(result["junction_removed"], 1)
|
||||
assert not _is_junction(link), "stale junction must be removed"
|
||||
assert (dl / "shared" / "@ACE3").exists()
|
||||
|
||||
|
||||
def _test_migrate_missing_downloads_dir():
|
||||
comp = {"shared": {"mods": [{"name": "X", "steam_id": "1"}]}, "unique": {}}
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
result = _migrate(Path(d) / "nonexistent", None, comp)
|
||||
assert_eq(result["moved"], 0)
|
||||
assert_eq(result["skipped_not_found"], 1)
|
||||
|
||||
|
||||
test("migrator: already in correct group → no move", _test_migrate_already_correct)
|
||||
test("migrator: moves mod by steam_id to new group", _test_migrate_moves_by_steam_id)
|
||||
test("migrator: moves mod by normalized name (no meta.cpp)", _test_migrate_moves_by_normalized_name)
|
||||
test("migrator: skips when destination already exists", _test_migrate_skips_dest_exists)
|
||||
test("migrator: skips mod not on disk", _test_migrate_skips_not_on_disk)
|
||||
test("migrator: removes stale junction before moving", _test_migrate_removes_stale_junction)
|
||||
test("migrator: no-op when downloads dir missing", _test_migrate_missing_downloads_dir)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Summary
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user