diff --git a/check_names.py b/check_names.py index b53f7ff..e06810c 100644 --- a/check_names.py +++ b/check_names.py @@ -8,20 +8,24 @@ Matching strategy (most-reliable first): 2. Normalize folder name (@CBA_A3 -> cbaa3) -> server by_name Status codes: - OK disk name matches server name exactly - MISMATCH disk name differs from server canonical name - NOT_ON_SERVER server has no folder for this mod (new mod? typo?) + OK disk name matches server name exactly + MISMATCH disk name differs from server canonical name + NOT_ON_SERVER server has no folder for this mod (new mod? typo?) + ID_COLLISION local meta.cpp has wrong steam_id (belongs to a different mod) Usage: - python check_names.py # report only - python check_names.py --fix # rename + fix junctions - python check_names.py --group shared --fix + python check_names.py # report only + python check_names.py --fix # rename mismatched folders + fix junctions + python check_names.py --fix-ids # fix wrong steam_ids in local meta.cpp files + python check_names.py --fix --fix-ids # both + python check_names.py --group shared --fix-ids """ from __future__ import annotations import argparse -import os +import json +import re import sys from pathlib import Path @@ -29,7 +33,7 @@ from arma_modlist_tools.compat import fix_console_encoding from arma_modlist_tools.config import load_config from arma_modlist_tools.fetcher import ( _normalize_name, _parse_meta_cpp, - build_server_index, make_session, + build_server_index, ) from arma_modlist_tools.linker import _is_junction, create_junction, remove_junction @@ -37,21 +41,21 @@ fix_console_encoding() # Column widths W_DISK = 44 -W_SERVER = 44 +W_SERVER = 50 W_GROUP = 24 +_PUBLISHEDID_RE = re.compile(r"(publishedid\s*=\s*)\d+(\s*;)", re.IGNORECASE) + # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _server_name_from_url(url: str) -> str: - """Extract the folder name from a trailing-slash server URL.""" return url.rstrip("/").split("/")[-1] def _read_local_steam_id(mod_dir: Path) -> str | None: - """Try to read publishedid from meta.cpp inside a local mod folder.""" for candidate in (mod_dir / "meta.cpp", mod_dir / "Meta.cpp"): if candidate.exists(): try: @@ -61,28 +65,35 @@ def _read_local_steam_id(mod_dir: Path) -> str | None: return None -def _lookup_server_name(local_name: str, mod_dir: Path, index: dict) -> str | None: +def _lookup_detailed( + local_name: str, + mod_dir: Path, + index: dict, +) -> tuple[str | None, str | None]: """ - Return the server's canonical folder name for this local mod, or None. - Tries steam_id first, then normalized name. - """ - # 1. Steam ID from local meta.cpp (most reliable) - steam_id = _read_local_steam_id(mod_dir) - if steam_id: - url = index["by_steam_id"].get(steam_id) - if url: - return _server_name_from_url(url) + Return (server_name, local_steam_id_used). - # 2. Normalized name fallback + - server_name: canonical server folder name, or None if not found + - local_steam_id_used: the steam_id that was read from local meta.cpp + (present regardless of whether the lookup succeeded, so callers can + report what the wrong ID is) + """ + local_sid = _read_local_steam_id(mod_dir) + + if local_sid: + url = index["by_steam_id"].get(local_sid) + if url: + return _server_name_from_url(url), local_sid + + # Name-based fallback url = index["by_name"].get(_normalize_name(local_name)) if url: - return _server_name_from_url(url) + return _server_name_from_url(url), local_sid - return None + return None, local_sid def _collect_mod_folders(cfg, group_filter: str | None) -> list[tuple[str, Path]]: - """Return list of (group_name, mod_path) for every @Mod in downloads/.""" result = [] if not cfg.downloads.is_dir(): return result @@ -98,26 +109,30 @@ def _collect_mod_folders(cfg, group_filter: str | None) -> list[tuple[str, Path] # --------------------------------------------------------------------------- -# Classification helpers +# Classification # --------------------------------------------------------------------------- def _resolve_status( disk_name: str, server_name: str | None, + local_sid: str | None, ok_disk_names: set[str], ) -> tuple[str, str]: """ - Return (status, display_col) for one mod, after false-positive filtering. + Return (status, display_col) after false-positive filtering. - A MISMATCH is downgraded to NOT_ON_SERVER when the proposed server name is - already the exact disk name of another folder (steam_id collision on server). + ID_COLLISION: steam_id lookup returned a name that's already correctly + held by a different folder — the local meta.cpp has the wrong publishedid. """ if server_name is None: return "NOT_ON_SERVER", "---" if server_name == disk_name: return "OK", server_name if server_name in ok_disk_names: - return "NOT_ON_SERVER", "--- (steam_id collision)" + # This folder's local steam_id belongs to a mod that already has a + # correct folder on disk — the meta.cpp is wrong. + sid_info = f" (local id: {local_sid})" if local_sid else "" + return "ID_COLLISION", f"{server_name}{sid_info}" return "MISMATCH", server_name @@ -131,17 +146,10 @@ def _fix_mismatch( server_name: str, cfg, ) -> tuple[bool, str]: - """ - Rename mod_dir to server_name and update the arma_dir junction. - - Returns (success, message). - """ new_dir = mod_dir.parent / server_name - if new_dir.exists(): return False, f"target already exists: {new_dir}" - # --- Remove old junction in arma_dir (if present) --- old_link = cfg.arma_dir / mod_dir.name removed_old_link = False if _is_junction(old_link): @@ -150,26 +158,83 @@ def _fix_mismatch( return False, f"could not remove old junction {old_link}: {err}" removed_old_link = True - # --- Rename the download folder --- try: mod_dir.rename(new_dir) except OSError as exc: - # Try to restore the junction we just removed if removed_old_link: create_junction(old_link, mod_dir) return False, f"rename failed: {exc}" - # --- Create new junction in arma_dir --- new_link = cfg.arma_dir / server_name if cfg.arma_dir.exists(): - if _is_junction(new_link) or new_link.exists(): - pass # already correct or non-junction exists; leave it - else: + if not (_is_junction(new_link) or new_link.exists()): create_junction(new_link, new_dir) return True, f"{mod_dir.name} -> {server_name}" +# --------------------------------------------------------------------------- +# Fix: rewrite wrong steam_id in local meta.cpp +# --------------------------------------------------------------------------- + +def _build_comparison_id_map(cfg) -> dict[str, tuple[str, str]]: + """ + Load comparison.json and return {normalized_name: (steam_id, display_name)} + for every mod that has a steam_id. + """ + if not cfg.comparison.exists(): + return {} + try: + data = json.loads(cfg.comparison.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + return {} + + result: dict[str, tuple[str, str]] = {} + all_mods = list(data.get("shared", {}).get("mods", [])) + for preset_data in data.get("unique", {}).values(): + all_mods.extend(preset_data.get("mods", [])) + + for mod in all_mods: + sid = mod.get("steam_id") + name = mod.get("name", "") + if sid and name: + result[_normalize_name(name)] = (sid, name) + return result + + +def _write_steam_id(mod_dir: Path, new_sid: str) -> tuple[bool, str]: + """ + Overwrite / insert publishedid in mod_dir/meta.cpp. + Returns (success, message). + """ + meta_path = mod_dir / "meta.cpp" + if meta_path.exists(): + try: + text = meta_path.read_text(encoding="utf-8", errors="ignore") + except OSError as exc: + return False, str(exc) + + old_sid_match = _parse_meta_cpp(text) + if _PUBLISHEDID_RE.search(text): + new_text = _PUBLISHEDID_RE.sub(rf"\g<1>{new_sid}\g<2>", text) + else: + new_text = text.rstrip() + f"\npublishedid = {new_sid};\n" + + try: + meta_path.write_text(new_text, encoding="utf-8") + except OSError as exc: + return False, str(exc) + + old_str = old_sid_match or "missing" + return True, f"meta.cpp: {old_str} -> {new_sid}" + else: + try: + meta_path.write_text(f"publishedid = {new_sid};\n", encoding="utf-8") + except OSError as exc: + return False, str(exc) + return True, f"meta.cpp created with id {new_sid}" + + # --------------------------------------------------------------------------- # Main # --------------------------------------------------------------------------- @@ -178,12 +243,14 @@ def main() -> None: cfg = load_config() parser = argparse.ArgumentParser( - description="Check and optionally fix mod folder name mismatches." + description="Check and fix mod folder name mismatches and wrong steam IDs." ) parser.add_argument("--group", "-g", metavar="GROUP", help="Only check this group (default: all)") parser.add_argument("--fix", action="store_true", - help="Rename mismatched folders and fix junctions") + help="Rename MISMATCH folders and fix junctions") + parser.add_argument("--fix-ids", action="store_true", + help="Rewrite wrong publishedid in meta.cpp using comparison.json") args = parser.parse_args() # ---- Build server index ---- @@ -198,74 +265,116 @@ def main() -> None: print("No @Mod folders found in downloads/.\n") sys.exit(0) - # ---- Pass 1: raw lookup for every mod ---- - # result row: (group, mod_dir, server_name | None) - raw: list[tuple[str, Path, str | None]] = [] + # ---- Pass 1: raw lookup ---- + # (group, mod_dir, server_name | None, local_steam_id | None) + raw: list[tuple[str, Path, str | None, str | None]] = [] for group, mod_dir in mods: - raw.append((group, mod_dir, _lookup_server_name(mod_dir.name, mod_dir, index))) + server_name, local_sid = _lookup_detailed(mod_dir.name, mod_dir, index) + raw.append((group, mod_dir, server_name, local_sid)) - # ---- Pass 2: discard false positives caused by steam_id collisions ---- - # If a server name is already an exact disk name (i.e. an OK match exists), - # any other folder whose steam_id lookup points to that same server name is a - # false positive — the server has a bad/shared publishedid in meta.cpp. - # Reclassify those entries as NOT_ON_SERVER. + # ---- Pass 2: build ok_disk_names for collision detection ---- ok_disk_names: set[str] = { mod_dir.name - for _, mod_dir, server_name in raw - if server_name == mod_dir.name # exact match = genuinely OK + for _, mod_dir, server_name, _ in raw + if server_name == mod_dir.name } - def _classify(disk_name: str, server_name: str | None) -> tuple[str, str]: - return _resolve_status(disk_name, server_name, ok_disk_names) # ---- Print table ---- - ok_count = mismatch_count = unknown_count = 0 - mismatches: list[tuple[str, Path, str]] = [] + ok_count = mismatch_count = collision_count = unknown_count = 0 + mismatches: list[tuple[str, Path, str]] = [] + collisions: list[tuple[str, Path, str | None]] = [] # (group, mod_dir, local_sid) - print(f" {'Disk name':<{W_DISK}} {'Group':<{W_GROUP}} {'Server name':<{W_SERVER}} Status") - print(f" {'-'*W_DISK} {'-'*W_GROUP} {'-'*W_SERVER} ------") + print(f" {'Disk name':<{W_DISK}} {'Group':<{W_GROUP}} {'Status / Server name':<{W_SERVER}}") + print(f" {'-'*W_DISK} {'-'*W_GROUP} {'-'*W_SERVER}") - for group, mod_dir, server_name in raw: - status, server_col = _classify(mod_dir.name, server_name) + for group, mod_dir, server_name, local_sid in raw: + status, server_col = _resolve_status(mod_dir.name, server_name, local_sid, ok_disk_names) if status == "OK": ok_count += 1 + detail = f"OK ({server_col})" elif status == "MISMATCH": mismatch_count += 1 mismatches.append((group, mod_dir, server_name)) + detail = f"MISMATCH -> {server_col}" + elif status == "ID_COLLISION": + collision_count += 1 + collisions.append((group, mod_dir, local_sid)) + detail = f"ID_COLLISION {server_col}" else: unknown_count += 1 + detail = "NOT_ON_SERVER" - print(f" {mod_dir.name:<{W_DISK}} {group:<{W_GROUP}} {server_col:<{W_SERVER}} {status}") + print(f" {mod_dir.name:<{W_DISK}} {group:<{W_GROUP}} {detail}") - print(f"\n {ok_count} OK, {mismatch_count} mismatch, {unknown_count} not on server\n") + print(f"\n {ok_count} OK, {mismatch_count} mismatch, " + f"{collision_count} id_collision, {unknown_count} not on server\n") - if not mismatches: - if mismatch_count == 0: - print(" All folder names match the server. Safe to run full pipeline.\n") + any_action = args.fix or args.fix_ids + nothing_to_do = (not mismatches) and (not collisions) + + if nothing_to_do: + if mismatch_count == 0 and collision_count == 0: + print(" All folder names match. Safe to run full pipeline.\n") sys.exit(0) - # ---- Fix mode ---- - if not args.fix: - print(" Run with --fix to rename mismatched folders and update junctions.\n") + if not any_action: + hints = [] + if mismatches: + hints.append("--fix to rename mismatched folders") + if collisions: + hints.append("--fix-ids to correct wrong steam IDs in meta.cpp") + print(" Run with " + " and ".join(hints) + ".\n") sys.exit(0) - print(f"Fixing {len(mismatches)} mismatch(es)...\n") - fixed = failed = 0 + # ---- --fix: rename mismatched folders ---- + if args.fix and mismatches: + print(f"Fixing {len(mismatches)} name mismatch(es)...\n") + fixed = failed = 0 + for group, mod_dir, server_name in mismatches: + ok, msg = _fix_mismatch(group, mod_dir, server_name, cfg) + if ok: + print(f" [+] {msg}") + fixed += 1 + else: + print(f" [X] {mod_dir.name} FAILED: {msg}") + failed += 1 + print(f"\n Done: {fixed} renamed" + (f", {failed} failed" if failed else "") + "\n") - for group, mod_dir, server_name in mismatches: - ok, msg = _fix_mismatch(group, mod_dir, server_name, cfg) - if ok: - print(f" [+] {msg}") - fixed += 1 - else: - print(f" [X] {mod_dir.name} FAILED: {msg}") - failed += 1 + # ---- --fix-ids: rewrite wrong steam_ids ---- + if args.fix_ids and collisions: + id_map = _build_comparison_id_map(cfg) + if not id_map: + print(" WARNING: comparison.json not found or empty. " + "Run 'python run.py --skip-fetch --skip-link' first.\n") + sys.exit(1) - print(f"\n Done: {fixed} renamed" - + (f", {failed} failed" if failed else "") - + "\n") + print(f"Fixing {len(collisions)} steam_id collision(s)...\n") + fixed = failed = unknown = 0 - if fixed: - print(" Run 'python run.py --skip-fetch' to verify links are correct.\n") + for group, mod_dir, bad_sid in collisions: + norm = _normalize_name(mod_dir.name) + match = id_map.get(norm) + if match is None: + print(f" [?] {mod_dir.name:<{W_DISK}} not in comparison.json — skipped") + unknown += 1 + continue + correct_sid, display_name = match + if bad_sid == correct_sid: + print(f" [=] {mod_dir.name:<{W_DISK}} id already correct ({correct_sid})") + fixed += 1 + continue + ok, msg = _write_steam_id(mod_dir, correct_sid) + if ok: + print(f" [+] {mod_dir.name:<{W_DISK}} {msg}") + fixed += 1 + else: + print(f" [X] {mod_dir.name:<{W_DISK}} FAILED: {msg}") + failed += 1 + + print(f"\n Done: {fixed} fixed" + + (f", {unknown} skipped (not in comparison.json)" if unknown else "") + + (f", {failed} failed" if failed else "") + "\n") + print(" Re-run check_names.py to verify.\n") if __name__ == "__main__": diff --git a/test_suite.py b/test_suite.py index f601d9d..c60384a 100644 --- a/test_suite.py +++ b/test_suite.py @@ -1064,7 +1064,11 @@ test("all exported symbols are callable or types", _test_exports_are_callable) group("check_names helpers") -from check_names import _server_name_from_url, _read_local_steam_id, _lookup_server_name, _resolve_status +from check_names import ( + _server_name_from_url, _read_local_steam_id, + _lookup_detailed, _resolve_status, _write_steam_id, + _build_comparison_id_map, +) def _test_server_name_from_url(): @@ -1091,7 +1095,7 @@ def _test_read_local_steam_id_no_id_in_file(): assert _read_local_steam_id(d) is None -def _test_lookup_server_name_by_steam_id(): +def _test_lookup_detailed_by_steam_id(): index = { "by_steam_id": {"463939057": "https://x.com/@ace3/"}, "by_name": {}, @@ -1099,23 +1103,23 @@ def _test_lookup_server_name_by_steam_id(): with tempfile.TemporaryDirectory() as d: d = Path(d) (d / "meta.cpp").write_text("publishedid = 463939057;", encoding="utf-8") - result = _lookup_server_name("@ACE3", d, index) - assert_eq(result, "@ace3") + server_name, local_sid = _lookup_detailed("@ACE3", d, index) + assert_eq(server_name, "@ace3") + assert_eq(local_sid, "463939057") -def _test_lookup_server_name_by_name_fallback(): +def _test_lookup_detailed_by_name_fallback(): index = { "by_steam_id": {}, "by_name": {"cbaa3": "https://x.com/@cba_a3/"}, } with tempfile.TemporaryDirectory() as d: - # No meta.cpp — name fallback - result = _lookup_server_name("@CBA_A3", Path(d), index) - assert_eq(result, "@cba_a3") + server_name, local_sid = _lookup_detailed("@CBA_A3", Path(d), index) + assert_eq(server_name, "@cba_a3") + assert local_sid is None # no meta.cpp -def _test_lookup_server_name_steam_id_beats_name(): - """steam_id lookup must take priority over name fallback.""" +def _test_lookup_detailed_steam_id_beats_name(): index = { "by_steam_id": {"111": "https://x.com/@correct/"}, "by_name": {"mymod": "https://x.com/@wrong/"}, @@ -1123,55 +1127,149 @@ def _test_lookup_server_name_steam_id_beats_name(): with tempfile.TemporaryDirectory() as d: d = Path(d) (d / "meta.cpp").write_text("publishedid = 111;", encoding="utf-8") - result = _lookup_server_name("@MyMod", d, index) - assert_eq(result, "@correct") + server_name, local_sid = _lookup_detailed("@MyMod", d, index) + assert_eq(server_name, "@correct") + assert_eq(local_sid, "111") -def _test_lookup_server_name_not_found(): +def _test_lookup_detailed_not_found(): index = {"by_steam_id": {}, "by_name": {}} with tempfile.TemporaryDirectory() as d: - result = _lookup_server_name("@Unknown", Path(d), index) - assert result is None + server_name, local_sid = _lookup_detailed("@Unknown", Path(d), index) + assert server_name is None -test("_server_name_from_url: extracts name from URL", _test_server_name_from_url) -test("_read_local_steam_id: reads meta.cpp", _test_read_local_steam_id_present) -test("_read_local_steam_id: no meta.cpp -> None", _test_read_local_steam_id_missing) -test("_read_local_steam_id: meta.cpp without id -> None", _test_read_local_steam_id_no_id_in_file) -test("_lookup_server_name: matches by steam_id", _test_lookup_server_name_by_steam_id) -test("_lookup_server_name: falls back to name", _test_lookup_server_name_by_name_fallback) -test("_lookup_server_name: steam_id beats name fallback", _test_lookup_server_name_steam_id_beats_name) -test("_lookup_server_name: not found -> None", _test_lookup_server_name_not_found) +def _test_lookup_detailed_returns_sid_even_when_not_on_server(): + """local_sid should be returned even when server lookup fails.""" + index = {"by_steam_id": {}, "by_name": {}} + with tempfile.TemporaryDirectory() as d: + d = Path(d) + (d / "meta.cpp").write_text("publishedid = 999;", encoding="utf-8") + server_name, local_sid = _lookup_detailed("@SomeMod", d, index) + assert server_name is None + assert_eq(local_sid, "999") -# _resolve_status tests (the false-positive filter) + +test("_lookup_detailed: matches by steam_id", _test_lookup_detailed_by_steam_id) +test("_lookup_detailed: falls back to name", _test_lookup_detailed_by_name_fallback) +test("_lookup_detailed: steam_id beats name fallback", _test_lookup_detailed_steam_id_beats_name) +test("_lookup_detailed: not found -> None", _test_lookup_detailed_not_found) +test("_lookup_detailed: returns local_sid even when not on server", _test_lookup_detailed_returns_sid_even_when_not_on_server) + +# _resolve_status tests def _test_resolve_status_ok(): - status, col = _resolve_status("@ace", "@ace", ok_disk_names={"@ace"}) + status, col = _resolve_status("@ace", "@ace", None, ok_disk_names={"@ace"}) assert_eq(status, "OK") assert_eq(col, "@ace") def _test_resolve_status_mismatch(): - status, col = _resolve_status("@ACE3", "@ace3", ok_disk_names={"@cba_a3"}) + status, col = _resolve_status("@ACE3", "@ace3", None, ok_disk_names={"@cba_a3"}) assert_eq(status, "MISMATCH") assert_eq(col, "@ace3") def _test_resolve_status_not_found(): - status, col = _resolve_status("@Unknown", None, ok_disk_names=set()) + status, col = _resolve_status("@Unknown", None, None, ok_disk_names=set()) assert_eq(status, "NOT_ON_SERVER") -def _test_resolve_status_steam_id_collision(): - # server_name is already correctly held by another folder → false positive +def _test_resolve_status_id_collision(): ok = {"@Realistic Ragdoll Physics"} - status, col = _resolve_status("@NIArms All in One- ACE Compatibility", - "@Realistic Ragdoll Physics", - ok_disk_names=ok) - assert_eq(status, "NOT_ON_SERVER", "Should be reclassified due to collision") - assert "collision" in col + status, col = _resolve_status( + "@NIArms All in One- ACE Compatibility", + "@Realistic Ragdoll Physics", + "1234567", + ok_disk_names=ok, + ) + assert_eq(status, "ID_COLLISION") + assert "1234567" in col # bad steam_id shown in output + +def _test_resolve_status_collision_without_sid(): + ok = {"@Realistic Ragdoll Physics"} + status, col = _resolve_status( + "@Some Mod", "@Realistic Ragdoll Physics", None, ok_disk_names=ok + ) + assert_eq(status, "ID_COLLISION") test("_resolve_status: OK match", _test_resolve_status_ok) test("_resolve_status: genuine mismatch", _test_resolve_status_mismatch) test("_resolve_status: not found", _test_resolve_status_not_found) -test("_resolve_status: steam_id collision reclassified as NOT_ON_SERVER", _test_resolve_status_steam_id_collision) +test("_resolve_status: ID_COLLISION shows bad steam_id", _test_resolve_status_id_collision) +test("_resolve_status: ID_COLLISION without local sid", _test_resolve_status_collision_without_sid) + +# _write_steam_id tests + +def _test_write_steam_id_updates_existing(): + with tempfile.TemporaryDirectory() as d: + d = Path(d) + meta = d / "meta.cpp" + meta.write_text('publishedid = 111;\nname = "OldMod";\n', encoding="utf-8") + ok, msg = _write_steam_id(d, "999") + assert ok + assert "999" in meta.read_text(encoding="utf-8") + assert "111" not in meta.read_text(encoding="utf-8") + assert 'name = "OldMod"' in meta.read_text(encoding="utf-8") # preserved + +def _test_write_steam_id_creates_if_missing(): + with tempfile.TemporaryDirectory() as d: + d = Path(d) + ok, msg = _write_steam_id(d, "12345") + assert ok + meta = d / "meta.cpp" + assert meta.exists() + assert "12345" in meta.read_text(encoding="utf-8") + +def _test_write_steam_id_appends_if_no_line(): + with tempfile.TemporaryDirectory() as d: + d = Path(d) + meta = d / "meta.cpp" + meta.write_text('name = "NoId";\n', encoding="utf-8") + ok, msg = _write_steam_id(d, "777") + assert ok + content = meta.read_text(encoding="utf-8") + assert "777" in content + assert 'name = "NoId"' in content + +test("_write_steam_id: updates existing publishedid", _test_write_steam_id_updates_existing) +test("_write_steam_id: creates meta.cpp if missing", _test_write_steam_id_creates_if_missing) +test("_write_steam_id: appends if no publishedid line", _test_write_steam_id_appends_if_no_line) + +# _build_comparison_id_map tests + +def _test_build_comparison_id_map_reads_json(): + comparison = { + "compared_presets": ["A", "B"], + "shared": {"mod_count": 1, "mods": [ + {"name": "CBA_A3", "steam_id": "450814997", "url": None, "source": "steam"}, + ]}, + "unique": { + "A": {"mod_count": 1, "mods": [ + {"name": "ACE3", "steam_id": "463939057", "url": None, "source": "steam"}, + ]}, + "B": {"mod_count": 0, "mods": []}, + }, + } + with tempfile.TemporaryDirectory() as d: + cfg_data = { + "server": {"base_url": "x", "username": "u", "password": "p"}, + "paths": {"arma_dir": d, "downloads": d, + "modlist_html": d, "modlist_json": d}, + } + import json as _json + cfg_file = Path(d) / "config.json" + cfg_file.write_text(_json.dumps(cfg_data), encoding="utf-8") + from arma_modlist_tools.config import load_config as _lc + cfg = _lc(str(cfg_file)) + # Write a fake comparison.json + comp_path = Path(d) / "comparison.json" + comp_path.write_text(_json.dumps(comparison), encoding="utf-8") + id_map = _build_comparison_id_map(cfg) + + assert_in("cbaa3", id_map) + assert_eq(id_map["cbaa3"][0], "450814997") + assert_in("ace3", id_map) + assert_eq(id_map["ace3"][0], "463939057") + +test("_build_comparison_id_map: reads steam_ids from comparison.json", _test_build_comparison_id_map_reads_json) # ---------------------------------------------------------------------------