Outbox dispatcher (background thread, FOR UPDATE SKIP LOCKED claim, exponential backoff, no transaction held during HTTP I/O). n8n callback endpoint with shared-secret auth and idempotency by event ID. Automation nav + UI with manual retry. 49 backend tests passing, ruff clean. Fixed a crash-on-redelivery bug in seeded outbox payloads and made the dispatcher defensive against malformed payloads. Verified the full live round trip against a real n8n instance: return -> outbox -> dispatcher -> n8n workflow -> callback -> succeeded, including the S5 failed-retry demo scenario.
39 lines
1.5 KiB
Python
39 lines
1.5 KiB
Python
def test_list_workflows_requires_operations_manager(employee_client):
|
|
response = employee_client.get("/api/v1/workflows")
|
|
assert response.status_code == 403
|
|
|
|
|
|
def test_list_workflows_includes_seeded_failed_run(ops_client):
|
|
response = ops_client.get("/api/v1/workflows", params={"status": "failed"})
|
|
assert response.status_code == 200
|
|
runs = response.json()
|
|
assert len(runs) >= 1
|
|
assert all(r["status"] == "failed" for r in runs)
|
|
|
|
|
|
def test_retry_requires_failed_status(ops_client):
|
|
succeeded = ops_client.get("/api/v1/workflows", params={"status": "succeeded"}).json()
|
|
target = succeeded[0]["event_id"]
|
|
response = ops_client.post(f"/api/v1/workflows/{target}/retry")
|
|
assert response.status_code == 409
|
|
assert response.json()["error"]["code"] == "NOT_RETRYABLE"
|
|
|
|
|
|
def test_retry_failed_run_moves_to_pending_and_audits(ops_client):
|
|
failed = ops_client.get("/api/v1/workflows", params={"status": "failed"}).json()
|
|
target = failed[0]["event_id"]
|
|
|
|
response = ops_client.post(f"/api/v1/workflows/{target}/retry")
|
|
assert response.status_code == 200
|
|
assert response.json()["status"] == "pending"
|
|
|
|
audit_events = ops_client.get("/api/v1/audit", params={"action": "workflow_retry"}).json()
|
|
assert len(audit_events) >= 1
|
|
|
|
|
|
def test_retry_requires_operations_manager(employee_client):
|
|
response = employee_client.post(
|
|
"/api/v1/workflows/00000000-0000-4000-8000-000000000020/retry"
|
|
)
|
|
assert response.status_code == 403
|