Phase 2 and 3 of the platform rework. - Neutral request/response contract: external_id, metadata, X-AI-Key, run_id in the response; client-specific fields removed. - AI profile is a revision table: saving creates an inactive revision, activation is separate, try-run does not store a run. One chat-completions gateway serves AIHub and OpenAI-compatible profiles. - Add between and in rule operators. - Contracts moved to contracts/lead-scoring/ with a score-response fixture. - Admin Web updated for revisions, runs and the neutral fixture. - Consolidate tests; remove rewrite_architect.md and the stale handoff doc; rewrite spec, CONTEXT and ARCHITECTURE for the current design. Not verified: live AIHub/OpenAI behavior, Omni/EMS integration.
48 lines
1.6 KiB
Python
48 lines
1.6 KiB
Python
from types import SimpleNamespace
|
|
|
|
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(canonical_request, 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": "f81c7c4e-a92a-4b19-8d7a-3a5c08eb4e6e",
|
|
}
|
|
response = client.post("/v1/lead-scoring/score", headers=headers, json=canonical_request)
|
|
assert response.status_code == 503
|
|
assert response.json() == {"detail": "Database unavailable"}
|
|
assert "private" not in response.text
|