From 0de2a961557cea2fa28ce8337e230bcc33cfc459 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Sat, 22 Aug 2026 16:35:40 +0200 Subject: [PATCH] Cover tool/interop_seed.py at 100% branch Third module of the tool/ coverage task, and the last small one. Patched at the module's own boundary (firebase_client_for, FirebaseConfig.load and sync_log), so nothing touches Firebase. The assertions are deliberately about the *shape* handed to sync_log rather than about sync_log working -- test_sync.py already covers the latter. This tool exists so crdt_sync_dart/tool/interop_check.dart has a Python-written record to find, so the things worth pinning are the ones that would make the two halves silently miss each other: the _interop/devices prefix, the pydev device id, the py-rec id with its exact value and Hlc, the library's own codec rather than a local reimplementation, and the in-memory revision store that keeps the seed repeatable after the Dart side deletes both devices' data. crdt_sync 286 -> 293 passed, still 100%. 3 of 7 tool/ modules done; the config flip stays last. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01CFSGeWxHPY5rHUGrPqhcmG --- .../crdt_sync/tests/test_tool_interop_seed.py | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 crdt-sync/crdt_sync/tests/test_tool_interop_seed.py diff --git a/crdt-sync/crdt_sync/tests/test_tool_interop_seed.py b/crdt-sync/crdt_sync/tests/test_tool_interop_seed.py new file mode 100644 index 0000000..9543a10 --- /dev/null +++ b/crdt-sync/crdt_sync/tests/test_tool_interop_seed.py @@ -0,0 +1,134 @@ +"""Tests for ``tool/interop_seed``, the Python half of the interop check. + +Patched at the module's own boundary -- ``firebase_client_for``, +``FirebaseConfig.load`` and ``sync_log`` -- so nothing touches Firebase. What +is worth asserting here is not that ``sync_log`` works (``test_sync.py`` covers +that) but that this tool hands it *exactly* the shape the Dart side looks for: +the seed exists to make ``crdt_sync_dart/tool/interop_check.dart`` pass, and a +drifted path prefix, device id or record id would make the two halves silently +miss each other, which is the failure the pair exists to catch. +""" + +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING +from unittest.mock import MagicMock, patch + +import pytest + +from crdt_sync import Hlc, Record +from tool import interop_seed + +if TYPE_CHECKING: + from collections.abc import Iterator + + +@pytest.fixture +def seeded() -> Iterator[tuple[int, MagicMock, MagicMock, MagicMock]]: + """Run ``main()`` with the network boundary mocked out. + + Yields the exit code plus the three mocks whose call arguments are what + the assertions below are actually about. + """ + with ( + patch.object(interop_seed, "firebase_client_for") as client_for, + patch.object(interop_seed.FirebaseConfig, "load") as load, + patch.object(interop_seed, "sync_log", return_value={"py-rec": None}) as synced, + ): + code = interop_seed.main() + yield code, synced, client_for, load + + +def test_main_reports_success(seeded: tuple) -> None: + """A successful seed is exit code 0.""" + code, _, _, _ = seeded + + assert code == 0 + + +def test_the_client_is_built_for_the_interop_app_from_the_shared_config( + seeded: tuple, +) -> None: + """The app name selects the credential cache; interop has its own.""" + _, _, client_for, load = seeded + + load.assert_called_once_with() + assert client_for.call_args.args == ("interop",) + assert client_for.call_args.kwargs["config"] is load.return_value + + +def test_the_target_matches_what_the_dart_checker_reads(seeded: tuple) -> None: + """The path prefix and device id the Dart half looks under. + + ``interop_check.dart`` reads ``_interop/devices``; a drift on either side + makes the check fail as "Python wrote nothing" rather than as a mismatch. + """ + _, synced, client_for, _ = seeded + target = synced.call_args.args[0] + + assert target.path_prefix == "_interop/devices" + assert target.device_id == "pydev" + assert target.client is client_for.return_value + + +def test_the_seeded_record_is_the_one_the_dart_side_asserts_on( + seeded: tuple, +) -> None: + """py-rec, with the value and Hlc the Dart checker compares against.""" + _, synced, _, _ = seeded + records = synced.call_args.args[1] + + assert set(records) == {"py-rec"} + record = records["py-rec"] + assert isinstance(record, Record) + assert record.id == "py-rec" + value, hlc = record.fields["value"] + assert value == "written-by-python" + assert hlc == Hlc(wall_time_ms=1000, counter=0, node_id="node-py") + + +def test_the_librarys_own_codec_is_used_rather_than_a_local_one( + seeded: tuple, +) -> None: + """Reimplementing the encoding here would test the wrong thing. + + The check is that what the real apps write is what the other language + reads, so the codec must be the library's canonical pair. + """ + _, synced, _, _ = seeded + codec = synced.call_args.args[2] + + assert codec.decode is interop_seed.load_log + assert codec.encode is interop_seed.dump_log + + +def test_revision_state_is_in_memory_so_reruns_reseed(seeded: tuple) -> None: + """A persisted cursor would make a second run write nothing. + + The Dart half deletes both devices' data on its way out, so the seed has + to be repeatable; remembering a revision across runs would break that. + """ + _, synced, _, _ = seeded + tracking = synced.call_args.args[3] + + assert isinstance(tracking.state_store, interop_seed.MemorySyncStateStore) + + +def test_main_reports_what_python_can_see_and_the_next_command( + caplog: pytest.LogCaptureFixture, +) -> None: + """The output is the operator's only signal that the seed landed.""" + with ( + patch.object(interop_seed, "firebase_client_for"), + patch.object(interop_seed.FirebaseConfig, "load"), + patch.object( + interop_seed, "sync_log", return_value={"py-rec": None, "a": None} + ), + caplog.at_level(logging.INFO, logger="interop-seed"), + ): + interop_seed.main() + + messages = [record.getMessage() for record in caplog.records] + assert any("['a', 'py-rec']" in message for message in messages) + assert any("dart run tool/interop_check.dart" in message for message in messages)