85 lines
4.1 KiB
Python
85 lines
4.1 KiB
Python
"""tjwater_v2 source/event atomicity checks; every write is rolled back."""
|
|
from contextlib import contextmanager
|
|
import os
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
|
|
from samples.peripheral.auth import Identity
|
|
from samples.peripheral.config import SampleSettings
|
|
from samples.peripheral.errors import SampleFailure
|
|
from samples.peripheral.schemas import CreateWorkOrder, DeleteResource, UpdateWorkOrder
|
|
from samples.peripheral.store import PostgresStore
|
|
|
|
|
|
pytestmark = pytest.mark.skipif(
|
|
os.getenv("PERIPHERAL_SAMPLE_LIVE_TEST") != "1", reason="explicit live sample opt-in required",
|
|
)
|
|
|
|
|
|
def test_work_order_mutations_and_events_are_atomic_idempotent_and_rollback_cleanly(monkeypatch):
|
|
settings = SampleSettings()
|
|
assert settings.db_name == "tjwater_v2", "event verification is restricted to tjwater_v2"
|
|
store = PostgresStore(settings)
|
|
who = Identity(str(uuid4()), str(uuid4()), True)
|
|
with store.connection() as conn:
|
|
@contextmanager
|
|
def connection(*, readonly=False, timeseries=False):
|
|
assert timeseries is False
|
|
with conn.transaction():
|
|
yield conn
|
|
|
|
monkeypatch.setattr(store, "connection", connection)
|
|
with conn.transaction(force_rollback=True):
|
|
command = CreateWorkOrder(
|
|
request_id=uuid4(), title="AUTOTEST push work order", summary="isolated transaction",
|
|
)
|
|
created = store.create_work_order(who, command)
|
|
assert store.create_work_order(who, command) == created
|
|
order_id = created["work_order_id"]
|
|
store.update_work_order(who, order_id, UpdateWorkOrder(
|
|
request_id=uuid4(), expected_revision=1, status="in_progress",
|
|
))
|
|
store.update_work_order(who, order_id, UpdateWorkOrder(
|
|
request_id=uuid4(), expected_revision=2, progress_note="AUTOTEST field feedback",
|
|
))
|
|
with pytest.raises(SampleFailure) as stale:
|
|
store.update_work_order(who, order_id, UpdateWorkOrder(
|
|
request_id=uuid4(), expected_revision=1, status="completed",
|
|
))
|
|
assert stale.value.code == "REVISION_CONFLICT"
|
|
store.delete_work_order(who, order_id, DeleteResource(
|
|
request_id=uuid4(), expected_revision=3,
|
|
))
|
|
rows = conn.execute(
|
|
"""SELECT project_id,topic,aggregate_type,aggregate_id,aggregate_revision,metadata
|
|
FROM workbench.domain_event_log WHERE project_id=%s ORDER BY event_seq""",
|
|
(who.project_id,),
|
|
).fetchall()
|
|
assert len(rows) == 4
|
|
assert [row["aggregate_revision"] for row in rows] == [1, 2, 3, 4]
|
|
assert [row["metadata"]["change"] for row in rows] == ["created", "updated", "updated", "deleted"]
|
|
assert all(row["topic"] == "workbench.work_order.changed"
|
|
and row["aggregate_type"] == "peripheral_work_order"
|
|
and row["aggregate_id"] == order_id for row in rows)
|
|
assert store.list_work_orders(who, 20)["items"] == []
|
|
|
|
original = store._work_order_event
|
|
|
|
def fail_after_event(*args, **kwargs):
|
|
original(*args, **kwargs)
|
|
raise RuntimeError("AUTOTEST transaction failure")
|
|
|
|
monkeypatch.setattr(store, "_work_order_event", fail_after_event)
|
|
with pytest.raises(RuntimeError, match="transaction failure"):
|
|
store.create_work_order(who, command.model_copy(update={"request_id": uuid4()}))
|
|
assert store.list_work_orders(who, 20)["items"] == []
|
|
assert conn.execute(
|
|
"SELECT count(*) AS n FROM workbench.domain_event_log WHERE project_id=%s", (who.project_id,),
|
|
).fetchone()["n"] == 4
|
|
|
|
for table in ("workbench.domain_event_log", "peripheral_sample.work_orders",
|
|
"peripheral_sample.work_order_revisions", "peripheral_sample.requests"):
|
|
assert conn.execute(f"SELECT count(*) AS n FROM {table} WHERE project_id=%s",
|
|
(who.project_id,)).fetchone()["n"] == 0
|