80 lines
2.3 KiB
Python
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
|