from types import SimpleNamespace import pytest def test_native_translation_and_mod_edits_rebuild_the_same_vm_input(tmp_path, monkeypatch): from app import mods from app.config import get_settings from app.converter.nani_package import Package, text_records from app.games.tomie.convert import convert_package from app.games.tomie.format import unpack_modules from app.games.tomie.profile import PROFILE from app.translation import apply_changes, locale_tables, text_index monkeypatch.setenv("DATA_DIR", str(tmp_path / "data")) get_settings.cache_clear() package = Package(profile=PROFILE, defaults={"points": 3}, function_data={"names": ["Tomie"]}) def script(name, label, commands): return {"name": name, "lines": [{"line": 1, "label": label, "commands": commands}]} package.scripts = { "StartGameSetting": script("StartGameSetting", "Start", [ {"op": "PrintText", "args": {"Text": {"template": "Points: {points}"}}}, {"op": "ExitToTitle", "args": {}}]), "MessagePlay": script("MessagePlay", "Start", [{"op": "Return", "args": {}}]), "AppScreen": script("AppScreen", "Start", [{"op": "Stop", "args": {}}]), } package.texts = text_records("Tips", "tip:Title|Description {points}", "Tips") media = {key: {} for key in ("backgrounds", "characters", "textures", "videos", "audio", "fonts")} monkeypatch.setattr("app.games.tomie.convert.export_media", lambda *_: ({}, media, {})) try: result = convert_package(tmp_path, tmp_path / "work", lambda _: None, package) index = text_index(result.manifest) assert {line.source for line in index.lines} == {"Points: {points}", "Title", "Description {points}"} line = next(line for line in index.lines if line.source.startswith("Points:")) translated = apply_changes(result.manifest, "ru", lines={line.tid: {"t": "Баллы: {points}"}}) assert locale_tables(translated, "ru")[0][line.tid]["t"] == "Баллы: {points}" doc = mods.document(translated) mods.validate(doc, translated) node = next(node for module in doc["scripts"].values() for label in module["labels"] for node in label["body"] if node.get("tid") == line.tid) node["what"] = "Score: {points}" updated = mods.store(doc, translated) runtime = unpack_modules(mods.document(updated)["scripts"]) assert runtime["StartGameSetting"]["commands"][0]["args"]["Text"] == {"template": "Score: {points}"} module = next(module for module in doc["scripts"].values() if module.get("nani", {}).get("name") == "StartGameSetting") module["labels"][0]["body"].insert(0, {"id": "inserted", "op": "nani_command", "command": { "op": "SetCustomVariable", "args": {"Expression": "points+=1"}}}) assert unpack_modules(doc["scripts"])["StartGameSetting"]["labels"]["Start"] == 1 mods.validate(doc, translated) node["command"]["op"] = "NativePlugin" with pytest.raises(ValueError, match="NativePlugin"): mods.validate(doc, translated) finally: get_settings.cache_clear() def test_video_recovery_checks_ranges_and_copies_only_declared_bytes(tmp_path): from app.games.tomie.assets import recover_object data = tmp_path / "data" out = tmp_path / "out" data.mkdir(); out.mkdir() original = b"header-clip-trailer" (data / "resources.resource").write_bytes(original) tree = {"m_OriginalPath": "Assets/Video/clip.webm", "m_ExternalResources": { "m_Source": "resources.resource", "m_Offset": 7, "m_Size": 4}} obj = SimpleNamespace(type=SimpleNamespace(name="VideoClip"), read=lambda: SimpleNamespace(m_Name="Clip"), read_typetree=lambda: tree) recovered = recover_object(obj, data, out, "video") assert recovered["path"].read_bytes() == b"clip" assert (data / "resources.resource").read_bytes() == original tree["m_ExternalResources"]["m_Size"] = len(original) with pytest.raises(ValueError, match="диапазон"): recover_object(obj, data, out, "invalid")