tenant-engine/tests/test_audit_core.py
tegwick 672cf4da6e
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 37s
Implement TEN-WP-0011 security layer conformance
Engine/PIP declaration is now checkable (layer.yaml plus a Tooling-client
scan). Writes persist a decision record or the published fail-closed
stance, live-lookup freshness is published, events_for is tenant-scoped,
and mutation evidence drains to audit-core from a local outbox without
blocking the mutation.

Sender registration is requested as AUDIT-IN-0002. Boundary-contract
amendment is requested as NET-IN-0002.

Assistant: grok
Assistant-Session: 01a04cea-e5e8-7081-a0fc-808ebbc35fa9
2026-08-29 13:02:51 +02:00

84 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