feat(work-orders): synchronize peripheral changes through events
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""Opt-in checks against provisioned sample tables, never business-table writes."""
|
||||
"""Opt-in checks against provisioned sample tables and transactional source events."""
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import os
|
||||
import subprocess
|
||||
@@ -39,6 +39,10 @@ def sample():
|
||||
yield store, who
|
||||
# Only this test's unguessable project key; tables created by the authorized setup.
|
||||
with store.connection() as conn:
|
||||
if store.settings.db_name == "tjwater_v2" and conn.execute(
|
||||
"SELECT to_regclass('workbench.domain_event_log') AS name"
|
||||
).fetchone()["name"] is not None:
|
||||
conn.execute("DELETE FROM workbench.domain_event_log WHERE project_id=%s", (who.project_id,))
|
||||
for table in (
|
||||
"work_order_revisions", "test_data_record_revisions", "work_orders",
|
||||
"test_data_records", "feedback", "requests", "revisions", "tasks", "scenarios",
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user