Files
mrleo1nidandClaude Sonnet 5.5 f49ee90477 feat: browser access (allowed_origins, CORS with Private Network Access), job cancel, Tomie/Unity adapters, benchmark
Includes earlier uncommitted work: Tomie and Unity adapters, benchmark, structured output, call metrics.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 18:59:28 +03:00

149 lines
7.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import base64
import hashlib
import json
from pathlib import Path
import struct
import tempfile
import unittest
from unittest.mock import patch
from local_translator.benchmark import write_json
from local_translator.tomie import STATE, append_managed_records, atomic_copy, digest, inside, install, original_sources, patch_catalog, patch_lock, restore, rewrite_dictionary
class DictionaryTests(unittest.TestCase):
def test_missing_runtime_graphics_labels_are_added_as_managed_records(self):
key = "Naninovel.UI.GameSettingsGraphicsDropdown.GraphicOption5"
result = append_managed_records("Key: Value\r\n", {key: "Очень высокое"})
self.assertEqual(result, f"Key: Value\r\n{key}: Очень высокое\r\n")
with self.assertRaises(ValueError):
append_managed_records(result, {key: "Duplicate"})
with self.assertRaises(ValueError):
append_managed_records("Key: Value", {key: "A\nB"})
def test_preserves_keys_whitespace_comments_and_crlf(self):
source = '; comment\r\nMenu.Load: LOAD\r\nOther: value: extra\r\n'
result = rewrite_dictionary(source, {"Menu.Load": "ЗАГРУЗИТЬ"})
self.assertEqual(result, '; comment\r\nMenu.Load: ЗАГРУЗИТЬ\r\nOther: value: extra\r\n')
def test_unknown_duplicate_and_multiline_values_rejected(self):
for source, replacements in [("Key: A", {"Other": "B"}), ("Key: A\nKey: B", {"Key": "В"}), ("Key: A", {"Key": "А\nБ"})]:
with self.assertRaises(ValueError):
rewrite_dictionary(source, replacements)
def test_catalog_offsets_preserved_and_crc_actually_updated(self):
old_hash, new_hash = "a" * 32, "b" * 32
record = json.dumps({"m_Hash": old_hash, "m_Crc": 2659166264, "m_BundleSize": 37229646}, separators=(",", ":")).encode("utf-16-le")
payload = b"header" + struct.pack("<I", len(record)) + record + b"unrelated-second-record"
document = {"m_ExtraDataString": base64.b64encode(payload).decode(), "other": "keep"}
result, before, after = patch_catalog(document, old_hash, 12345, 36999999, new_hash)
raw = base64.b64decode(result["m_ExtraDataString"])
self.assertEqual(len(raw), len(payload))
self.assertTrue(raw.endswith(b"unrelated-second-record"))
self.assertEqual(before["m_Crc"], 2659166264)
self.assertEqual(after["m_Crc"], 12345)
self.assertEqual(after["m_Hash"], new_hash)
self.assertEqual(result["other"], "keep")
with self.assertRaises(ValueError):
patch_catalog(document, old_hash, 12345, 999999999, new_hash)
class InstallerTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.root = Path(self.temp.name)
self.game = self.root / "game"
self.package = self.root / "package"
self.game.mkdir()
(self.game / "TomieWGM.exe").write_bytes(b"fake executable, never run")
self.records = []
for name in ("data.unity3d", "StreamingAssets/aa/catalog.json"):
relative = Path("TomieWGM_Data") / name
original = self.game / relative
payload = self.package / "payload" / relative
original.parent.mkdir(parents=True, exist_ok=True)
payload.parent.mkdir(parents=True, exist_ok=True)
original.write_bytes(("original " + name).encode())
payload.write_bytes(("translated " + name).encode())
self.records.append({"path": relative.as_posix(), "before": digest(original), "after": digest(payload)})
write_json(self.package / "manifest.json", {"id": "patch-1", "files": self.records})
def tearDown(self):
self.temp.cleanup()
def test_install_is_idempotent_and_restore_is_byte_exact(self):
install(self.package, self.game, lambda: False)
state = json.loads((self.game / STATE).read_text(encoding="utf-8"))
self.assertEqual(state["phase"], "installed")
for row in state["files"]:
self.assertEqual(digest(self.game / row["path"]), row["after"])
self.assertEqual(digest(self.game / row["backup"]), row["before"])
install(self.package, self.game, lambda: False)
restore(self.game, lambda: False)
for row in self.records:
self.assertEqual(digest(self.game / row["path"]), row["before"])
self.assertEqual(json.loads((self.game / STATE).read_text())["phase"], "restored")
def test_running_game_blocks_both_actions(self):
with self.assertRaises(RuntimeError):
install(self.package, self.game, lambda: True)
with self.assertRaises(RuntimeError):
restore(self.game, lambda: True)
self.assertFalse((self.game / STATE).exists())
def test_foreign_original_and_damaged_payload_are_rejected_before_writes(self):
target = self.game / self.records[-1]["path"]
target.write_bytes(b"foreign mod")
with self.assertRaises(ValueError):
install(self.package, self.game, lambda: False)
self.assertEqual(digest(self.game / self.records[0]["path"]), self.records[0]["before"])
target.write_bytes(b"original StreamingAssets/aa/catalog.json")
(self.package / "payload" / self.records[-1]["path"]).write_bytes(b"tampered")
with self.assertRaises(ValueError):
install(self.package, self.game, lambda: False)
self.assertFalse((self.game / STATE).exists())
def test_partial_install_failure_rolls_back_already_replaced_files(self):
def fail_second(source, target, expected):
if Path(source).resolve().is_relative_to((self.package / "payload").resolve()) and Path(target).name == "catalog.json":
raise OSError("simulated disk failure")
return atomic_copy(source, target, expected)
with patch("local_translator.tomie.atomic_copy", fail_second), self.assertRaises(OSError):
install(self.package, self.game, lambda: False)
self.assertEqual(json.loads((self.game / STATE).read_text())["phase"], "restored")
for row in self.records:
self.assertEqual(digest(self.game / row["path"]), row["before"])
def test_restore_refuses_to_overwrite_new_foreign_changes(self):
install(self.package, self.game, lambda: False)
target = self.game / self.records[0]["path"]
target.write_bytes(b"new unrelated mod")
with self.assertRaises(ValueError):
restore(self.game, lambda: False)
self.assertEqual(target.read_bytes(), b"new unrelated mod")
def test_paths_and_concurrent_installers_are_guarded(self):
for relative in ("../outside", str(self.root.resolve())):
with self.assertRaises(ValueError):
inside(self.game, relative)
with patch_lock(self.game):
with self.assertRaises(RuntimeError):
with patch_lock(self.game):
self.fail("Two installers acquired one lock")
def test_rebuild_uses_verified_original_backups_without_changing_installed_game(self):
install(self.package, self.game, lambda: False)
inputs = [self.game.resolve() / row["path"] for row in self.records]
sources, live_hashes = original_sources(self.game.resolve(), inputs)
for row, path in zip(self.records, inputs):
self.assertEqual(digest(sources[path]), row["before"])
self.assertEqual(live_hashes[path], row["after"])
self.assertEqual(digest(path), row["after"])
inputs[0].write_bytes(b"foreign edit")
with self.assertRaises(ValueError):
original_sources(self.game.resolve(), inputs)
if __name__ == "__main__":
unittest.main()