diff --git a/bridge/cutter/rea_bridge.py b/bridge/cutter/rea_bridge.py index 473f1d5fe6..c0b77e17e8 100644 --- a/bridge/cutter/rea_bridge.py +++ b/bridge/cutter/rea_bridge.py @@ -333,8 +333,8 @@ class _RequestDispatcher(QObject): @staticmethod def _read_current_file() -> str | None: try: - value = cutter.core().currentlyOpenFile() - return str(value) if value else None + value = cutter.core().getConfig("file.path") + return value if isinstance(value, str) and value else None except Exception: return None diff --git a/tests/fixtures/cutter_bridge_protocol_test.py b/tests/fixtures/cutter_bridge_protocol_test.py index ae98769191..0f617e6b6d 100644 --- a/tests/fixtures/cutter_bridge_protocol_test.py +++ b/tests/fixtures/cutter_bridge_protocol_test.py @@ -14,6 +14,7 @@ import types import unittest import uuid from pathlib import Path +from unittest.mock import patch def _install_host_stubs(): @@ -22,7 +23,7 @@ def _install_host_stubs(): sys.modules["cutter"] = cutter qtcore = types.ModuleType("PySide6.QtCore") qtcore.QObject = type("QObject", (), {}) - qtcore.Signal = lambda *_args: object() + qtcore.Signal = lambda *_args: types.SimpleNamespace(connect=lambda _handler: None) pyside = types.ModuleType("PySide6") pyside.QtCore = qtcore sys.modules["PySide6"] = pyside @@ -221,6 +222,64 @@ def run_serve_mode(): server.join(timeout=3) +class DocumentIdentityTest(unittest.TestCase): + def test_document_config_drives_generation_and_command_admission(self): + first = "/tmp/Cutter 雪 #1?.elf" + second = "/tmp/another document.elf" + configuration = {"file.path": first} + core = types.SimpleNamespace( + getConfig=configuration.__getitem__, + getVersionInformation=lambda: "backend fixture", + ) + commands = [] + with patch.object(bridge.cutter, "core", lambda: core, create=True), patch.object( + bridge.cutter, "cmd", lambda command: commands.append(command) or "observed", create=True + ): + dispatcher = bridge._RequestDispatcher() + self.assertEqual(dispatcher.current_file, first) + self.assertEqual(dispatcher.generation, 0) + + def handle(**values): + request = {**values, "_event": threading.Event(), "_cancelled": threading.Event()} + reply = {} + dispatcher._handle(request, reply) + self.assertTrue(request["_event"].is_set()) + return reply + + same = handle(kind="status") + self.assertEqual(same["current_file"], first) + self.assertEqual(same["document_generation"], 0) + configuration["file.path"] = second + changed = handle(kind="status") + self.assertEqual(changed["current_file"], second) + self.assertEqual(changed["document_generation"], 1) + stale = handle(kind="command", request_id=str(uuid.uuid4()), expected_generation=0, command="iI") + self.assertEqual(stale["error"], "active-document-generation-changed") + self.assertEqual(stale["execution_state"], "not_started") + self.assertEqual(commands, []) + current = handle(kind="command", request_id=str(uuid.uuid4()), expected_generation=1, command="iI") + self.assertEqual(current["output"], "observed") + self.assertEqual(current["document_generation"], 1) + self.assertEqual(commands, ["iI"]) + + def test_absent_or_unreadable_document_config_remains_unknown(self): + class UnreadableCore: + def getConfig(self, _key): + raise RuntimeError("document config unavailable") + + for core in [ + types.SimpleNamespace(getConfig=lambda _key: ""), + types.SimpleNamespace(getConfig=lambda _key: None), + types.SimpleNamespace(getConfig=lambda _key: object()), + types.SimpleNamespace(), + UnreadableCore(), + ]: + with self.subTest(core=type(core).__name__), patch.object( + bridge.cutter, "core", lambda: core, create=True + ): + self.assertIsNone(bridge._RequestDispatcher._read_current_file()) + + class ProtocolProducerTest(unittest.TestCase): def test_rejects_legacy_peers_before_ui_dispatch(self): with socket.create_connection(("127.0.0.1", self.server.port), timeout=2) as connection: