"""Migration test (Task 2): `products` -- confirms the table/columns exist via `information_schema` over the `create_all` schema (Teste A, same precedent as the auto's `test_fiscal_cadastro_schema.py`), and that a `Product` round-trips (INSERT cru via the real ORM/service) on a database built PURELY by `alembic upgrade head` (Teste B) -- never `Base.metadata.create_all`, which would never catch the migration itself drifting from the model.""" import pytest import pytest_asyncio from sqlalchemy import select, text from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine # Import de registro: garante que Product está registrado em Base.metadata # antes do create_all da fixture test_engine, ao rodar este arquivo isolado. from fiscal_svc.tenancy import models as _tenancy_models # noqa: F401 from tests.migrations._helpers import run_alembic, run_psql _MIGRATION_DB_NAME = "fiscal_svc_test_products_schema" _MIGRATION_DB_URL = ( f"postgresql+asyncpg://postgres:postgres@localhost:5432/{_MIGRATION_DB_NAME}" ) @pytest.mark.asyncio async def test_products_table_and_columns_exist(db_session): rows = { r[0]: r[1] for r in ( await db_session.execute( text( "select column_name, is_nullable from information_schema.columns " "where table_name='products'" ) ) ) } for required in ("id", "name", "api_key_hash", "webhook_secret", "created_at", "deleted_at"): assert required in rows, f"coluna {required} ausente em products" assert rows["name"] == "NO" assert rows["api_key_hash"] == "NO" assert rows["webhook_secret"] == "YES" assert rows["deleted_at"] == "YES" @pytest_asyncio.fixture async def migration_database(): run_psql("-c", f"DROP DATABASE IF EXISTS {_MIGRATION_DB_NAME} WITH (FORCE);") run_psql("-c", f"CREATE DATABASE {_MIGRATION_DB_NAME};") yield run_psql("-c", f"DROP DATABASE IF EXISTS {_MIGRATION_DB_NAME} WITH (FORCE);") @pytest.mark.asyncio async def test_product_round_trips_on_a_real_migrated_database(migration_database): result = run_alembic(_MIGRATION_DB_URL, "upgrade", "head") assert result.returncode == 0, result.stderr from fiscal_svc.tenancy.service import create_product engine = create_async_engine(_MIGRATION_DB_URL, echo=False) session_maker = async_sessionmaker(engine, expire_on_commit=False) try: async with session_maker() as session: product = await create_product(session, name="auto", api_key="plain-key-123") product_id = product.id # The plaintext key is NEVER what lands in the column. assert product.api_key_hash != "plain-key-123" async with session_maker() as session: from fiscal_svc.tenancy.models import Product reloaded = await session.get(Product, product_id) assert reloaded.name == "auto" assert reloaded.deleted_at is None result_q = await session.execute(select(Product).where(Product.name == "auto")) assert result_q.scalar_one().id == product_id finally: await engine.dispose()