feat: add native Tomie adapter and strict import compatibility checks

This commit is contained in:
mrleo1nid committed 2026-10-25 11:02:52 +03:00
1 parent c8d83b3fe7
commit 57a4edcbfd
45 files changed
+2985 -71

No files matched your search

+48 -2
View File
@@ -85,6 +85,23 @@ def test_unsupported_renpy_logic_never_degrades_to_ready(source, reason):
require_supported_scripts(data)
def test_renpy_atl_animation_is_rejected_instead_of_becoming_static():
data = script('transform moving:\n linear 1.0 xalign 1.0\nlabel start:\n "Hi"')
with pytest.raises(CompatibilityError, match="ATL"):
require_supported_scripts(data)
def test_source_encoding_is_preserved_or_explicitly_rejected(tmp_path):
from app.converter.source import read_source
from app.converter.rpy_lexer import ParseError
source = tmp_path / "script.rpy"
source.write_bytes('# coding: cp1251\nlabel start:\n "Привет"'.encode("cp1251"))
assert "Привет" in read_source(source)
source.write_bytes(b'label start:\n "\xff"')
with pytest.raises(ParseError, match="кодировку"):
read_source(source)
def test_directory_rejection_precedes_copying_media(tmp_path, monkeypatch):
write(tmp_path / "source/game/script.rpy", 'label start:\n call screen map')
def media(*args, **kwargs):
@@ -191,8 +208,9 @@ def test_naninovel_resources_keep_keys_delimiters_and_context():
records = text_records("Tips", "# comment\nHeading\nHelp:Title|Description\n", "Tips")
assert [r["text"] for r in records.values()] == ["Title", "Description"]
assert len(text_records("_synonyms", "word=hello,hi\n", "synonyms")) == 2
with pytest.raises(ValueError, match="Повторяющийся"):
text_records("DefaultUI", "Key:A\nKey:B", "UI")
duplicates = text_records("Tips", "Key:A\nKey:B", "Tips")
assert [r["text"] for r in duplicates.values()] == ["A", "B"]
assert [r["occurrence"] for r in duplicates.values()] == [0, 1]
info = LineInfo("id", "node", "script/resources.json", "resources", "", "<b>Hello {name}</b>", None,
resource={"delimiters": "|\r\n"})
validate_nani_translation(info, "<b>Привет {name}</b>")
@@ -241,6 +259,34 @@ def test_unity_codec_restores_global_hooks_after_error():
assert (helper.read_value, helper.write_value, helper.read_typetree_boost, helper.get_ref_type_node) == before
def test_unity_package_closes_cyclic_readers_and_optional_none_fields(tmp_path):
handle = (tmp_path / "asset").open("wb")
reader = SimpleNamespace(stream=handle, files=None, cabs=None)
asset = SimpleNamespace(reader=reader, files=None, cabs={"same": reader})
environment = SimpleNamespace(files={"asset": asset}, cabs={"asset": asset})
original = Package(environments=[environment], resources=[asset])
original.close()
assert handle.closed
assert not original.resources and not original.environments
(tmp_path / "asset").unlink()
def test_unused_renpy_translation_entries_are_not_script_blockers(tmp_path):
from app.converter import locales
from app.converter.rpy_parser import ScriptData
data = ScriptData()
data.modules = {"script.rpy": [{"name": "start", "body": [{"op": "say", "what": "Hello", "_ln": 1}]}]}
by_line = locales.assign_ids(data.modules)
path = tmp_path / "tl/russian/script.rpy"
path.parent.mkdir(parents=True)
path.write_text('# game/script.rpy:1\ntranslate russian start_123:\n "Привет"\n\n\n\n\n\n'
'translate russian removed_456:\n "Старая реплика"\n', encoding="utf-8")
translations = locales.import_tl(tmp_path, data.modules, by_line, data.warn)
assert translations["ru"].unmatched == 1
assert not data.blockers
assert any("реплики без пары" in message for message in data.warnings)
def test_upload_rejects_unsupported_engine_without_queueing(tmp_path, monkeypatch):
from app.routers import versions
from app.uploads import upload_path