Files
rune/tests/test_orchestration_projection.py
T

81 lines
2.7 KiB
Python

"""Incremental materialized view projection."""
from rune.orchestration.projection import ProjectionEngine, state_hash
from rune.orchestration.records import ProjectionCheckpoint
def _count_reducer(state: dict, event: dict) -> dict:
t = event["event_type"]
state[t] = state.get(t, 0) + 1
return state
def test_apply_accumulates_and_hash_changes():
engine = ProjectionEngine(_count_reducer)
engine.apply({"event_type": "a"})
first = engine.checkpoint()
engine.apply({"event_type": "a"})
second = engine.checkpoint()
assert engine.state() == {"a": 2}
assert second.offset == first.offset + 1
assert second.state_hash != first.state_hash
def test_rebuild_from_checkpoint_reproduces_exact_state():
engine = ProjectionEngine(_count_reducer)
events = [{"event_type": "a"}, {"event_type": "b"}, {"event_type": "a"}]
for ev in events:
engine.apply(ev)
cp = engine.checkpoint()
rebuilt = ProjectionEngine(_count_reducer).rebuild(events, cp)
assert rebuilt == engine.state()
assert state_hash(rebuilt) == cp.state_hash
def test_rebuild_from_scratch_without_checkpoint():
events = [{"event_type": "a"}, {"event_type": "b"}]
rebuilt = ProjectionEngine(_count_reducer).rebuild(events, None)
assert rebuilt == {"a": 1, "b": 1}
def test_hash_is_ordered_and_deterministic():
s1 = state_hash({"a": 1, "b": 2})
s2 = state_hash({"b": 2, "a": 1})
s3 = state_hash({"a": 2, "b": 1})
assert s1 == s2 # dict key order does not matter
assert s1 != s3
def test_rebuild_detects_truncated_log(tmp_path):
engine = ProjectionEngine(_count_reducer)
events = [{"event_type": "a"}, {"event_type": "b"}]
for ev in events:
engine.apply(ev)
cp = engine.checkpoint()
truncated = events[:1] # log lost the second event
try:
ProjectionEngine(_count_reducer).rebuild(truncated, cp)
raise AssertionError("expected ProjectionCorruptError")
except ProjectionEngine.ProjectionCorruptError:
pass
def test_rebuild_rejects_offset_beyond_log():
events = [{"event_type": "a"}]
cp = ProjectionCheckpoint(offset=5, state_hash="sha256:x")
try:
ProjectionEngine(_count_reducer).rebuild(events, cp)
raise AssertionError("expected ProjectionCorruptError")
except ProjectionEngine.ProjectionCorruptError:
pass
def test_rebuild_from_valid_checkpoint_still_passes():
engine = ProjectionEngine(_count_reducer)
events = [{"event_type": "a"}, {"event_type": "a"}]
for ev in events:
engine.apply(ev)
cp = engine.checkpoint()
rebuilt = ProjectionEngine(_count_reducer).rebuild(events, cp)
assert rebuilt == engine.state()