Add app credentials, account-scoped idempotency and per-app terminal notifications. Consolidate the unreleased migration baseline, preserve rule descriptions as AI context, and regenerate provider contracts with regression coverage.
54 lines
1.8 KiB
Python
54 lines
1.8 KiB
Python
from types import SimpleNamespace
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
from sqlalchemy.exc import OperationalError
|
|
|
|
from ai_platform.bootstrap.app import create_app
|
|
from ai_platform.bootstrap.wiring import get_app_credential_use_cases, get_scoring_use_cases
|
|
from ai_platform.infrastructure.config.settings import Settings
|
|
|
|
|
|
@pytest.mark.parametrize("boundary", ["auth", "scoring"])
|
|
@pytest.mark.parametrize(
|
|
"error",
|
|
[
|
|
OperationalError(
|
|
"private-sql", {"credential": "private-key"}, Exception("private-storage-error")
|
|
),
|
|
ConnectionRefusedError("private-storage-error"),
|
|
],
|
|
)
|
|
def test_database_unavailability_returns_safe_503_on_post_and_get(
|
|
valid_run_payload, boundary, error
|
|
):
|
|
app = create_app(Settings())
|
|
|
|
async def authenticate(_raw):
|
|
return "omni-local"
|
|
|
|
async def credential_cases():
|
|
if boundary == "auth":
|
|
raise error
|
|
return SimpleNamespace(authenticate=authenticate)
|
|
|
|
async def scoring_cases():
|
|
raise error
|
|
|
|
app.dependency_overrides[get_app_credential_use_cases] = credential_cases
|
|
app.dependency_overrides[get_scoring_use_cases] = scoring_cases
|
|
# Dependencies are isolated; this test makes no DB connections or startup mutations.
|
|
client = TestClient(app)
|
|
headers = {
|
|
"Authorization": "Bearer private-key",
|
|
"Idempotency-Key": valid_run_payload["request_id"],
|
|
}
|
|
for response in (
|
|
client.post("/v1/lead-scoring/runs", headers=headers, json=valid_run_payload),
|
|
client.get(f"/v1/lead-scoring/runs/{uuid4()}", headers=headers),
|
|
):
|
|
assert response.status_code == 503
|
|
assert response.json() == {"detail": "Database unavailable"}
|
|
assert "private" not in response.text
|