from pathlib import Path import pytest from app.converter.compatibility import Issue from app.games import registry from app.games.base import GameProfile from app.games.tomie.defects import KNOWN_DEFECTS, split_known def test_builtin_profiles_are_registered(): ids = {profile.id for profile in registry.all_profiles()} assert {"renpy", "naninovel", "tomie/expansion-1.3801"} <= ids assert registry.for_runtime("tomie/1").id == "tomie/expansion-1.3801" assert registry.for_runtime(None) is None assert registry.for_runtime("unknown/9") is None def test_specific_profile_wins_over_engine_profile(tmp_path, monkeypatch): assert registry.select("naninovel", tmp_path).id == "naninovel" # hash does not match monkeypatch.setattr("app.games.tomie.identify", lambda root: "tomie/expansion-1.3801") assert registry.select("naninovel", tmp_path).id == "tomie/expansion-1.3801" assert registry.select("renpy", tmp_path).id == "renpy" assert registry.select("godot", tmp_path) is None def test_new_profile_can_be_registered_once(monkeypatch): monkeypatch.setattr(registry, "_PROFILES", list(registry.all_profiles())) class Custom(GameProfile): id = "custom/game-1" engine = "naninovel" specific = True def claims(self, root: Path) -> bool: return root.name == "Custom" registry.register(Custom()) assert registry.select("naninovel", Path("x/Custom")).id == "custom/game-1" assert registry.select("naninovel", Path("x/Other")).id == "naninovel" with pytest.raises(ValueError, match="уже зарегистрирован"): registry.register(Custom()) def test_known_defects_are_waived_only_up_to_the_recorded_count(): (code, message, script), allowed = next(iter(KNOWN_DEFECTS.items())) known_issue = Issue(code, message, f"{script}:10") blocking, known = split_known([known_issue] * (allowed + 1)) assert len(known) == allowed and len(blocking) == 1 new = Issue("missing_target", "Не найден переход Nowhere.Start", "Common:1") blocking, known = split_known([new, known_issue]) assert blocking == [new] and known == [known_issue] same_message_other_script = Issue(code, message, "SomewhereElse:1") assert split_known([same_message_other_script])[0] == [same_message_other_script] def test_known_build_line_patch_requires_the_exact_line(): import hashlib from app.games import renpy_builds wrong = ' elif wake_up_after_sex=="spitroast" and number_of_people_had_sex_with_before_sleeping=2:' fixed = wrong.replace("=2:", "==2:") assert hashlib.sha256(wrong.encode()).hexdigest() in renpy_builds.BUILDS[ "bb19a33af00324fa027a629c052aee885e72f1b7bee7074403a5b9e20c69e2d8"]["lines"]["sleep.rpy"] source = f"label a:\r\n{wrong}\r\n pass\r\n" assert renpy_builds.prepare_source("renpy/the-only-doe-0.05", "Sleep.rpy", source) == \ f"label a:\r\n{fixed}\r\n pass\r\n" # another build, another file, or an edited line stays untouched assert renpy_builds.prepare_source(None, "sleep.rpy", source) == source assert renpy_builds.prepare_source("renpy/the-only-doe-0.05", "other.rpy", source) == source edited = source.replace("spitroast", "other") assert renpy_builds.prepare_source("renpy/the-only-doe-0.05", "sleep.rpy", edited) == edited def test_placeholder_media_is_skipped_only_on_an_exact_match(tmp_path): from app.games import renpy_builds icon = tmp_path / "gui" / "journal_icon.png" icon.parent.mkdir() icon.write_text("PLACEHOLDER: Download a journal icon from a free resource and save as journal_icon.png in gui folder.") assert renpy_builds.placeholder_files("renpy/the-only-doe-0.05", tmp_path) == {icon} assert renpy_builds.placeholder_files(None, tmp_path) == set() icon.write_text("a real file now") assert renpy_builds.placeholder_files("renpy/the-only-doe-0.05", tmp_path) == set()