Commit atomic domain and recovery state together
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from unittest.mock import patch
|
||||
|
||||
from sqlalchemy import create_engine, select
|
||||
from sqlalchemy import Column, MetaData, String, Table, create_engine, select
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
import pytest
|
||||
|
||||
@@ -70,6 +71,24 @@ def _start(factory, identity, *, key: str = "build-1"):
|
||||
)
|
||||
|
||||
|
||||
def _start_atomic(factory, identity, *, key: str = "sync-1"):
|
||||
return begin_durable_recovery_operation(
|
||||
factory,
|
||||
identity=identity,
|
||||
module_id="connectors",
|
||||
operation_type="read-snapshot",
|
||||
idempotency_key=key,
|
||||
request={"provider_id": "provider-1", "cursor": "revision-1"},
|
||||
recovery_plan=RecoveryPlan(
|
||||
mode=RecoveryMode.ATOMIC,
|
||||
preconditions=("the provider read is non-mutating",),
|
||||
verification_steps=("compare the committed projection",),
|
||||
),
|
||||
precondition_evidence={"provider_mutation": False},
|
||||
lease_resource_key="connectors:provider-1",
|
||||
)
|
||||
|
||||
|
||||
def test_durable_operation_commits_before_caller_effect_and_replays_success() -> None:
|
||||
engine, factory = _fixture()
|
||||
try:
|
||||
@@ -102,6 +121,74 @@ def test_durable_operation_commits_before_caller_effect_and_replays_success() ->
|
||||
engine.dispose()
|
||||
|
||||
|
||||
def test_atomic_terminal_commits_domain_rows_and_recovery_evidence_together() -> None:
|
||||
engine, factory = _fixture()
|
||||
metadata = MetaData()
|
||||
projection = Table(
|
||||
"test_recovery_projection",
|
||||
metadata,
|
||||
Column("id", String(36), primary_key=True),
|
||||
)
|
||||
metadata.create_all(engine)
|
||||
try:
|
||||
started = _start_atomic(factory, _identity("worker-1", "incarnation-1"))
|
||||
assert started.operation is not None
|
||||
with factory() as session:
|
||||
session.execute(projection.insert().values(id="projection-1"))
|
||||
started.operation.commit_atomic_success(
|
||||
session,
|
||||
evidence={
|
||||
"verified": True,
|
||||
"checks": {"projection_id": "projection-1"},
|
||||
},
|
||||
)
|
||||
|
||||
with factory() as session:
|
||||
assert session.scalar(select(projection.c.id)) == "projection-1"
|
||||
operation = session.get(RecoveryOperation, started.operation_id)
|
||||
assert operation is not None
|
||||
assert operation.status == RecoveryStatus.SUCCEEDED.value
|
||||
assert verify_recovery_evidence_chain(session, operation.id)
|
||||
finally:
|
||||
engine.dispose()
|
||||
|
||||
|
||||
def test_failed_atomic_commit_rolls_back_domain_and_terminal_checkpoint() -> None:
|
||||
engine, factory = _fixture()
|
||||
metadata = MetaData()
|
||||
projection = Table(
|
||||
"test_recovery_projection_rollback",
|
||||
metadata,
|
||||
Column("id", String(36), primary_key=True),
|
||||
)
|
||||
metadata.create_all(engine)
|
||||
try:
|
||||
started = _start_atomic(factory, _identity("worker-1", "incarnation-1"))
|
||||
assert started.operation is not None
|
||||
with factory() as session:
|
||||
session.execute(projection.insert().values(id="rolled-back"))
|
||||
with (
|
||||
patch.object(session, "commit", side_effect=RuntimeError("commit failed")),
|
||||
pytest.raises(RuntimeError, match="commit failed"),
|
||||
):
|
||||
started.operation.commit_atomic_success(
|
||||
session,
|
||||
evidence={
|
||||
"verified": True,
|
||||
"checks": {"projection_id": "rolled-back"},
|
||||
},
|
||||
)
|
||||
|
||||
with factory() as session:
|
||||
assert session.execute(select(projection.c.id)).all() == []
|
||||
operation = session.get(RecoveryOperation, started.operation_id)
|
||||
assert operation is not None
|
||||
assert operation.status == RecoveryStatus.RUNNING.value
|
||||
assert verify_recovery_evidence_chain(session, operation.id)
|
||||
finally:
|
||||
engine.dispose()
|
||||
|
||||
|
||||
def test_same_fence_cannot_start_duplicate_running_operation() -> None:
|
||||
engine, factory = _fixture()
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user