POST /v1/lead-scoring/score now evaluates in the request, applies an overall AI deadline, and stores the completed run only after scoring. Idempotency is kept by (app_id, account_id, request_id) with a body fingerprint. Removes the webhook outbox, scheduler/recovery loop, app lead_scoring permission, credential audit events, seed_demo and generate_secret scripts, and the tests that covered them. Trims tests to behavior of the new flow.
99 lines
4.5 KiB
Python
99 lines
4.5 KiB
Python
from importlib.util import module_from_spec, spec_from_file_location
|
|
from pathlib import Path
|
|
from urllib.parse import urlsplit
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
from alembic.autogenerate import compare_metadata
|
|
from alembic.config import Config
|
|
from alembic.migration import MigrationContext
|
|
from alembic.operations import Operations
|
|
from alembic.script import ScriptDirectory
|
|
from sqlalchemy import delete, insert, inspect, text
|
|
from sqlalchemy.exc import IntegrityError
|
|
|
|
from ai_platform.infrastructure.config.settings import Settings
|
|
from ai_platform.infrastructure.persistence.database import Base, make_session_factory
|
|
from ai_platform.infrastructure.persistence.models import (
|
|
AppCredential,
|
|
ScoringRun,
|
|
)
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def test_one_initial_migration_with_no_legacy_chain():
|
|
config = Config(str(ROOT / "alembic.ini"))
|
|
config.set_main_option("path_separator", "os")
|
|
config.set_main_option("script_location", str(ROOT / "migrations"))
|
|
scripts = ScriptDirectory.from_config(config)
|
|
revisions = list(scripts.walk_revisions())
|
|
assert len(revisions) == 1
|
|
assert revisions[0].revision == "0001_initial"
|
|
assert revisions[0].down_revision is None
|
|
assert scripts.get_heads() == ["0001_initial"]
|
|
|
|
|
|
async def test_initial_schema_matches_models_constraints_and_round_trip():
|
|
settings = Settings.load()
|
|
if urlsplit(settings.database_url).path.rstrip("/").rsplit("/", 1)[-1] != "ai_platform_test":
|
|
pytest.fail("Migration tests require isolated ai_platform_test")
|
|
path = ROOT / "migrations/versions/0001_initial.py"
|
|
spec = spec_from_file_location(path.stem, path)
|
|
assert spec is not None and spec.loader is not None
|
|
migration = module_from_spec(spec)
|
|
spec.loader.exec_module(migration)
|
|
factory = make_session_factory(settings.database_url)
|
|
schema = "migration_test_" + uuid4().hex
|
|
try:
|
|
async with factory.kw["bind"].connect() as connection:
|
|
transaction = await connection.begin()
|
|
try:
|
|
await connection.execute(text(f"CREATE SCHEMA {schema}"))
|
|
await connection.execute(text(f"SET LOCAL search_path TO {schema}"))
|
|
|
|
def apply(sync, direction):
|
|
with Operations.context(MigrationContext.configure(sync)):
|
|
getattr(migration, direction)()
|
|
|
|
def assert_schema(sync):
|
|
context = MigrationContext.configure(
|
|
sync, opts={"compare_server_default": True}
|
|
)
|
|
assert compare_metadata(context, Base.metadata) == []
|
|
assert set(inspect(sync).get_table_names()) == set(Base.metadata.tables)
|
|
|
|
await connection.run_sync(apply, "upgrade")
|
|
await connection.run_sync(assert_schema)
|
|
for app_id in ("app-a", "app-b"):
|
|
await connection.execute(
|
|
insert(AppCredential).values(app_id=app_id, verifier=app_id)
|
|
)
|
|
request_id, run_id = str(uuid4()), str(uuid4())
|
|
run = {
|
|
"app_id": "app-a",
|
|
"account_id": "3",
|
|
"request_id": request_id,
|
|
"fingerprint": "0" * 64,
|
|
"request": {},
|
|
"profile_version": "example",
|
|
}
|
|
await connection.execute(insert(ScoringRun).values(id=run_id, **run))
|
|
# The same request identity is valid for a different account or app.
|
|
await connection.execute(insert(ScoringRun).values(**{**run, "account_id": "4"}))
|
|
await connection.execute(insert(ScoringRun).values(**{**run, "app_id": "app-b"}))
|
|
for values in (run, {**run, "app_id": "unknown"}):
|
|
with pytest.raises(IntegrityError):
|
|
async with connection.begin_nested():
|
|
await connection.execute(insert(ScoringRun).values(**values))
|
|
await connection.execute(delete(ScoringRun).where(ScoringRun.id == run_id))
|
|
await connection.run_sync(apply, "downgrade")
|
|
assert await connection.run_sync(lambda sync: inspect(sync).get_table_names()) == []
|
|
await connection.run_sync(apply, "upgrade")
|
|
await connection.run_sync(assert_schema)
|
|
finally:
|
|
# Only this test's schema/data are reverted; public data is untouched.
|
|
await transaction.rollback()
|
|
finally:
|
|
await factory.kw["bind"].dispose()
|