import copy from uuid import uuid4 import pytest from app import mods from app.config import get_settings from app.converter.pipeline import convert_archive from app.translation import blob_json, file_json from tests.make_sample import build @pytest.fixture def pg_manifest(tmp_path, monkeypatch): monkeypatch.setenv("DATA_DIR", str(tmp_path / "data")) get_settings.cache_clear() blob_json.cache_clear() yield convert_archive(build(tmp_path / "sample.zip"), "sample.zip", tmp_path / "work", lambda _: None).manifest blob_json.cache_clear() get_settings.cache_clear() def node(id, text): return {"id": id, "op": "say", "tid": "t_" + id, "what": text} def merge(base, left, right, choices=None): conflicts = [] out = mods.merge_value(base, left, right, "", conflicts, choices or {}) return out, conflicts def test_editor_roundtrip_validation_and_anchors(pg_manifest): doc = mods.prepare(mods.document(pg_manifest)) assert doc == mods.prepare(mods.document(pg_manifest)) mods.validate(doc, pg_manifest) for module in doc["scripts"].values(): if module["labels"]: module["labels"][0]["body"].insert(0, node(str(uuid4()), "Inserted")) updated = mods.store(doc, pg_manifest) assert mods.comparable(mods.document(updated))["scripts"] == mods.comparable(doc)["scripts"] assert file_json(pg_manifest, "game.json") == doc["game"] damaged = copy.deepcopy(doc) first = next(m for m in damaged["scripts"].values() if m["labels"])["labels"][0] first["body"].append(copy.deepcopy(first["body"][0])) with pytest.raises(ValueError, match="ID"): mods.validate(damaged, pg_manifest) damaged = copy.deepcopy(doc) next(m for m in damaged["scripts"].values() if m["labels"])["labels"][0]["body"].append({"op": "jump", "id": "bad", "target": "missing_label"}) with pytest.raises(ValueError, match="метку"): mods.validate(damaged, pg_manifest) def test_merge_disjoint_fields_and_insertions(): base = [node("a", "A"), node("b", "B"), node("c", "C")] left = [node("a", "Changed"), node("x", "X"), node("b", "B"), node("c", "C")] right = [node("a", "A"), node("b", "B"), node("y", "Y"), node("c", "Changed too")] result, conflicts = merge(base, left, right) assert not conflicts assert [n["id"] for n in result] == ["a", "x", "b", "y", "c"] assert result[0]["what"] == "Changed" and result[-1]["what"] == "Changed too" def test_conflicting_edit_delete_and_order(): base = [node("a", "A"), node("b", "B")] left = [node("a", "LEFT"), node("b", "B")] right = [node("b", "B")] _, conflicts = merge(base, left, right) assert conflicts[0]["path"] == "/@a" and conflicts[0]["rightDeleted"] result, conflicts = merge(base, left, right, {"/@a": "right"}) assert result == right and not conflicts _, conflicts = merge(base, [base[0], node("x", "X"), base[1]], [base[0], node("y", "Y"), base[1]]) assert conflicts[0]["path"] == "/$order" def test_asset_conflict_keeps_hash_size_atomic(): base = {"hash": "a", "size": 10} left = {"hash": "b", "size": 10} right = {"hash": "c", "size": 20} assert merge(base, left, right, {"": "left"}) == (left, []) def test_malformed_menu_condition_rejected(pg_manifest): doc = mods.prepare(mods.document(pg_manifest)) from app.converter.locales import walk menu = next(n for m in doc["scripts"].values() for l in m["labels"] for n in walk(l["body"]) if n["op"] == "menu") menu["items"][0]["if"] = {"k": "run-javascript"} with pytest.raises(ValueError, match="выражение"): mods.validate(doc, pg_manifest) def test_optional_nani_expression_contract(): from app.pgvn import expression expression({"k": "nani", "ast": ["bin", "+", ["name", "score"], ["const", 1]]}) with pytest.raises(ValueError): expression({"k": "nani", "ast": ["call", "eval", [["const", "alert(1)"]]]}) def test_rebase_does_not_trust_positional_ids(): def doc(nodes): return {"game": {}, "scripts": {"script/a.json": {"labels": [{"id": "label", "name": "start", "body": nodes}]}}} base = doc([node("1", "A"), node("2", "B")]) target = doc([node("1", "Inserted"), node("2", "A"), node("3", "B")]) aligned, unmatched = mods.align(base, target) assert not unmatched nodes = aligned["scripts"]["script/a.json"]["labels"][0]["body"] assert nodes[0]["id"] not in ("1", "2") assert [n["id"] for n in nodes[1:]] == ["1", "2"] assert aligned == mods.align(base, target)[0] target["scripts"]["script/a.json"]["labels"][0]["body"][1]["what"] = "Updated A" _, unmatched = mods.align(base, target) assert unmatched[0]["id"] == "1" aligned, _ = mods.align(base, target, {"1": "2"}) assert aligned["scripts"]["script/a.json"]["labels"][0]["body"][1]["id"] == "1"