Files
rune/tests/test_orchestration_lifecycle.py
T

80 lines
2.3 KiB
Python

"""Outbox lifecycle: legal transitions, terminal states, attempt counts."""
import pytest
from rune.orchestration.lifecycle import InvalidTransition, OutboxLifecycle
from rune.orchestration.records import OutboxRecord
def test_advance_sending_to_accepted_returns_new_record():
lc = OutboxLifecycle()
r = OutboxRecord(
mutation_id="mut_a",
remote_target="t",
expected_remote_version=0,
state="sending",
attempts=1,
)
r2 = lc.advance(r, "accepted")
assert r2.state == "accepted"
assert r2.attempts == 1
assert r2 is not r # immutable-style: new record
def test_retryable_failure_increments_attempts_and_can_retry():
lc = OutboxLifecycle()
r = OutboxRecord(
mutation_id="mut_a",
remote_target="t",
expected_remote_version=0,
state="sending",
attempts=1,
)
fail = lc.advance(r, "retryable_failure")
assert fail.attempts == 2
retry = lc.advance(fail, "sending")
assert retry.state == "sending"
assert retry.attempts == 3
def test_illegal_transition_raises():
lc = OutboxLifecycle()
r = OutboxRecord(
mutation_id="mut_a",
remote_target="t",
expected_remote_version=0,
state="accepted",
)
with pytest.raises(InvalidTransition):
lc.advance(r, "queued")
def test_terminal_states_never_advance_and_can_reports_them():
lc = OutboxLifecycle()
for terminal in ("accepted", "conflict", "cancelled", "superseded"):
OutboxRecord(
mutation_id="m",
remote_target="t",
expected_remote_version=0,
state=terminal,
)
assert not lc.can(terminal, "sending")
assert lc.can("sending", terminal)
def test_crashed_sending_can_recover_via_self_loop():
# A crash between mark(sending) and mark(outcome) leaves the record in
# 'sending'; reconcile() must be able to re-advance it on restart.
lc = OutboxLifecycle()
r = OutboxRecord(
mutation_id="mut_a",
remote_target="t",
expected_remote_version=0,
state="sending",
attempts=1,
)
assert lc.can("sending", "sending")
retry = lc.advance(r, "sending")
assert retry.state == "sending"
assert retry.attempts == 2 # a fresh attempt after the crash