utils/crdt-sync/crdt_sync/tests/test_store.py
Krzysztof kuhy Rudnicki 5be99ab11d
Some checks are pending
crdt-sync pre-commit / pre-commit (push) Waiting to run
crdt-sync tests / test (3.10) (push) Waiting to run
crdt-sync tests / test (3.11) (push) Waiting to run
crdt-sync tests / test (3.12) (push) Waiting to run
Add LogStore local-persistence layer to crdt_sync (v0.2.0)
crdt_sync_dart: new domain-agnostic LogStore -- an in-memory Log with a
reactive changes stream and upsert/delete(sticky tombstone)/replaceAll,
behind an injectable LogPersistence port -- plus logToJson/logFromJson.
FileLogPersistence (atomic temp-file-then-rename, generalizing diet_guard's
LogStorageService) lives behind a separate crdt_sync_io.dart entrypoint so
the core barrel stays pure Dart / web-safe.

crdt-sync (Python): mirror dump_log/load_log/read_log/write_log sharing the
same on-disk JSON format so a log written on one side parses on the other.
Load/save only -- the reactive store is a Dart/Flutter convenience with no
Python equivalent by design (Python consumers are headless sync ticks).

Querying stays app-side: a Record's fields are opaque to the library, so
filtering/sorting/search is the caller's job over LogStore.values.

Both packages bumped to 0.2.0. 83 Dart tests + 71 Python tests (100% branch
coverage) green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019KwUCPESvE5CjCtmUFz8xQ
2026-07-11 22:19:34 +02:00

84 lines
2.5 KiB
Python

"""Tests for local Log persistence (dump/load and atomic file read/write)."""
from __future__ import annotations
import json
from typing import TYPE_CHECKING
import pytest
from crdt_sync import (
Record,
dump_log,
load_log,
read_log,
write_log,
)
if TYPE_CHECKING:
from collections.abc import Callable
from pathlib import Path
from crdt_sync import Hlc, Log
def _sample_log(make_hlc: Callable[..., Hlc]) -> Log:
return {
"a": Record(id="a", fields={"text": ("alpha", make_hlc(100))}),
"b": Record(id="b", fields={}, deleted=True, deleted_hlc=make_hlc(200)),
}
class TestDumpLoad:
def test_round_trips_a_log(self, make_hlc: Callable[..., Hlc]) -> None:
log = _sample_log(make_hlc)
assert load_log(dump_log(log)) == log
def test_load_raises_on_non_json(self) -> None:
# json.loads raises JSONDecodeError (a ValueError subclass).
with pytest.raises(json.JSONDecodeError):
load_log("{not json")
def test_load_raises_typeerror_on_wrong_shape(self) -> None:
# Valid JSON, but a list where an id->record map is expected.
with pytest.raises(TypeError):
load_log("[]")
class TestReadWrite:
def test_write_then_read_round_trips(
self, tmp_path: Path, make_hlc: Callable[..., Hlc]
) -> None:
log = _sample_log(make_hlc)
path = tmp_path / "nested" / "log.json"
write_log(path, log)
assert read_log(path) == log
def test_write_creates_parent_directories(
self, tmp_path: Path, make_hlc: Callable[..., Hlc]
) -> None:
path = tmp_path / "deep" / "nested" / "log.json"
write_log(path, _sample_log(make_hlc))
assert path.exists()
def test_write_overwrites_atomically(
self, tmp_path: Path, make_hlc: Callable[..., Hlc]
) -> None:
path = tmp_path / "log.json"
write_log(path, {"a": Record(id="a", fields={})})
write_log(path, _sample_log(make_hlc))
assert set(read_log(path)) == {"a", "b"}
def test_read_of_missing_file_is_empty(self, tmp_path: Path) -> None:
assert read_log(tmp_path / "nope.json") == {}
def test_read_of_corrupt_file_is_empty(self, tmp_path: Path) -> None:
path = tmp_path / "log.json"
path.write_text("{not json", encoding="utf-8")
assert read_log(path) == {}
def test_read_of_wrong_shape_file_is_empty(self, tmp_path: Path) -> None:
path = tmp_path / "log.json"
path.write_text("[]", encoding="utf-8")
assert read_log(path) == {}