dotb_ai-platform/tests/test_initial_migration.py
hungtran24 6577bb74be refactor: simplify scoring service, AI profile revisions and test suite
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.
2026-10-09 08:23:21 +07:00

72 lines
3.1 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 inspect, text
from ai_platform.infrastructure.config.settings import Settings
from ai_platform.infrastructure.persistence.database import Base, make_session_factory
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_and_round_trips():
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)
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()