111 lines
4.5 KiB
Python
111 lines
4.5 KiB
Python
import asyncio
|
|
import json
|
|
import time
|
|
from pathlib import Path
|
|
from urllib.parse import urlsplit
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
from sqlalchemy import delete, select
|
|
|
|
from ai_platform.bootstrap.app import create_app
|
|
from ai_platform.infrastructure.config.settings import Settings
|
|
from ai_platform.infrastructure.persistence.database import make_session_factory
|
|
from ai_platform.infrastructure.persistence.models import ScoringRun, TenantKey
|
|
from ai_platform.presentation.http.schemas import RunRequest
|
|
|
|
FIXTURE = Path(__file__).parent / "fixtures" / "mock_omni_run.json"
|
|
|
|
|
|
def isolated_test_settings() -> Settings:
|
|
settings = Settings.load()
|
|
parsed = urlsplit(settings.database_url)
|
|
database_name = parsed.path.rstrip("/").rsplit("/", 1)[-1]
|
|
if database_name != "ai_platform_test" or parsed.hostname not in {
|
|
"127.0.0.1",
|
|
"localhost",
|
|
"::1",
|
|
"postgres",
|
|
}:
|
|
pytest.skip("requires the dedicated local ai_platform_test database")
|
|
if settings.tenant_provisioning_secret is None:
|
|
pytest.skip("requires the local tenant provisioning secret")
|
|
return settings
|
|
|
|
|
|
def terminal_run(client: TestClient, run_id: str, headers: dict[str, str]) -> dict:
|
|
for _ in range(100):
|
|
response = client.get(f"/v1/lead-scoring/runs/{run_id}", headers=headers)
|
|
assert response.status_code == 200
|
|
result = response.json()
|
|
if result["status"] in {"completed", "failed"}:
|
|
return result
|
|
time.sleep(0.02)
|
|
pytest.fail("Scoring run did not reach a terminal state")
|
|
|
|
|
|
def test_api_seed_persists_and_reads_back_conversation_snapshot():
|
|
settings = isolated_test_settings()
|
|
provisioning_secret = settings.tenant_provisioning_secret
|
|
assert provisioning_secret is not None
|
|
payload = json.loads(FIXTURE.read_text())
|
|
expected_payload = RunRequest.model_validate(payload).model_dump(mode="json")
|
|
tenant_id = f"seed-persistence-{uuid4().hex}"
|
|
app = create_app(settings)
|
|
session_factory = make_session_factory(settings.database_url)
|
|
run_id: str | None = None
|
|
|
|
try:
|
|
with TestClient(app) as client:
|
|
provisioned = client.post(
|
|
"/v1/platform/tenant-keys",
|
|
headers={"Authorization": ("Bearer " + provisioning_secret.get_secret_value())},
|
|
json={"tenant_id": tenant_id},
|
|
)
|
|
assert provisioned.status_code == 201
|
|
tenant_headers = {
|
|
"Authorization": f"Bearer {provisioned.json()['tenant_key']}",
|
|
"Idempotency-Key": payload["request_id"],
|
|
}
|
|
|
|
admitted = client.post("/v1/lead-scoring/runs", headers=tenant_headers, json=payload)
|
|
assert admitted.status_code == 202
|
|
run_id = admitted.json()["run_id"]
|
|
assert isinstance(run_id, str)
|
|
completed = terminal_run(client, run_id, tenant_headers)
|
|
assert completed["status"] == "completed"
|
|
assert completed["ai_score"] is None
|
|
assert completed["degraded"] is False
|
|
assert completed["rule_score"] == 80
|
|
assert completed["final_score"] == 80
|
|
|
|
readback = client.get(f"/v1/lead-scoring/runs/{run_id}", headers=tenant_headers)
|
|
assert readback.status_code == 200
|
|
assert readback.json() == completed
|
|
api_snapshot = readback.json().get("snapshot")
|
|
if api_snapshot is None:
|
|
api_snapshot = readback.json().get("request", {}).get("snapshot")
|
|
assert api_snapshot == expected_payload["snapshot"]
|
|
finally:
|
|
|
|
async def verify_and_cleanup_created_rows() -> None:
|
|
try:
|
|
async with session_factory() as session:
|
|
if run_id is not None:
|
|
row = await session.scalar(
|
|
select(ScoringRun).where(
|
|
ScoringRun.id == run_id,
|
|
ScoringRun.tenant_id == tenant_id,
|
|
)
|
|
)
|
|
assert row is not None
|
|
assert row.request["snapshot"] == expected_payload["snapshot"]
|
|
await session.execute(delete(ScoringRun).where(ScoringRun.id == run_id))
|
|
await session.execute(delete(TenantKey).where(TenantKey.tenant_id == tenant_id))
|
|
await session.commit()
|
|
finally:
|
|
await session_factory.kw["bind"].dispose()
|
|
|
|
asyncio.run(verify_and_cleanup_created_rows())
|