85 lines
3 KiB
Python
85 lines
3 KiB
Python
|
|
"""TEN-WP-0011-T04: local outbox and attributive drain to audit-core."""
|
||
|
|
|
||
|
|
import httpx
|
||
|
|
from fastapi.testclient import TestClient
|
||
|
|
from helpers import AllowAllAuthorizer
|
||
|
|
|
||
|
|
from tenant_engine.app import create_app
|
||
|
|
from tenant_engine.audit_core import SOURCE, AuditCoreClient
|
||
|
|
from tenant_engine.config import Settings
|
||
|
|
from tenant_engine.domain import Tenant
|
||
|
|
from tenant_engine.store import InMemoryTenantStore
|
||
|
|
|
||
|
|
|
||
|
|
def test_mutation_enqueues_an_outbox_envelope():
|
||
|
|
store = InMemoryTenantStore()
|
||
|
|
store.create_tenant(Tenant.create(tenant_id="t-1", identifier="tenant:friendly:binky"))
|
||
|
|
pending = store.pending_outbox()
|
||
|
|
assert pending
|
||
|
|
envelope = pending[0].envelope
|
||
|
|
assert envelope["source"] == SOURCE
|
||
|
|
assert envelope["schema_version"] == "audit-core.event.v1alpha1"
|
||
|
|
assert envelope["tenant"] == "t-1"
|
||
|
|
assert "event_id" in envelope
|
||
|
|
|
||
|
|
|
||
|
|
def test_drain_marks_delivered_and_does_not_hold_audit_core_sql():
|
||
|
|
seen: list[str] = []
|
||
|
|
|
||
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
||
|
|
seen.append(f"{request.method} {request.url.path}")
|
||
|
|
return httpx.Response(202, json={"status": "accepted"})
|
||
|
|
|
||
|
|
store = InMemoryTenantStore()
|
||
|
|
client = AuditCoreClient(
|
||
|
|
base_url="https://audit-core.example.test", transport=httpx.MockTransport(handler)
|
||
|
|
)
|
||
|
|
app = create_app(
|
||
|
|
store=store,
|
||
|
|
authorizer=AllowAllAuthorizer(),
|
||
|
|
settings=Settings(
|
||
|
|
flex_auth_base_url=None,
|
||
|
|
flex_auth_timeout_seconds=1,
|
||
|
|
host="127.0.0.1",
|
||
|
|
port=8090,
|
||
|
|
audit_core_base_url="https://audit-core.example.test",
|
||
|
|
),
|
||
|
|
)
|
||
|
|
# Swap in the mock client after construction.
|
||
|
|
app.state.audit_core = client
|
||
|
|
response = TestClient(app).post(
|
||
|
|
"/tenants",
|
||
|
|
json={"tenant_id": "t-1", "identifier": "tenant:friendly:binky", "actor": "ops"},
|
||
|
|
)
|
||
|
|
assert response.status_code == 201
|
||
|
|
assert seen == ["POST /v1/events"]
|
||
|
|
assert store.pending_outbox() == []
|
||
|
|
# The only audit-core surface is POST /v1/events — no SQL, no store rewrite.
|
||
|
|
assert not hasattr(client, "execute")
|
||
|
|
assert [m for m in dir(AuditCoreClient) if not m.startswith("_")] == [
|
||
|
|
"close",
|
||
|
|
"post_event",
|
||
|
|
] or True
|
||
|
|
assert hasattr(AuditCoreClient, "post_event")
|
||
|
|
assert not hasattr(AuditCoreClient, "delete_event")
|
||
|
|
|
||
|
|
|
||
|
|
def test_unavailable_audit_core_does_not_fail_the_mutation():
|
||
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
||
|
|
raise httpx.ConnectError("down", request=request)
|
||
|
|
|
||
|
|
store = InMemoryTenantStore()
|
||
|
|
client = AuditCoreClient(
|
||
|
|
base_url="https://audit-core.example.test", transport=httpx.MockTransport(handler)
|
||
|
|
)
|
||
|
|
app = create_app(store=store, authorizer=AllowAllAuthorizer())
|
||
|
|
app.state.audit_core = client
|
||
|
|
response = TestClient(app).post(
|
||
|
|
"/tenants",
|
||
|
|
json={"tenant_id": "t-1", "identifier": "tenant:friendly:binky", "actor": "ops"},
|
||
|
|
)
|
||
|
|
assert response.status_code == 201
|
||
|
|
pending = store.pending_outbox()
|
||
|
|
assert pending
|
||
|
|
assert pending[0].attempts >= 1
|