test_billing_run_id_migration.py 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
  1. """Migration coverage for durable per-dispatch billing identities."""
  2. import pytest
  3. from sqlalchemy import text
  4. from sqlalchemy.ext.asyncio import create_async_engine
  5. import backend.app.models # noqa: F401 - populate Base.metadata
  6. import backend.app.models.external_link # noqa: F401 - required by a legacy ALTER in run_migrations
  7. import backend.app.models.print_log # noqa: F401 - required by a legacy ALTER in run_migrations
  8. from backend.app.core.database import Base, run_migrations
  9. @pytest.fixture(autouse=True)
  10. def force_sqlite_dialect(monkeypatch):
  11. """The engine below is SQLite, but settings.database_url may point at Postgres in a
  12. dev config — and run_migrations branches on the global dialect, not on the
  13. connection. Without this the Postgres branch runs against SQLite and the migration
  14. fails on Postgres-only syntax. Same fixture as test_ldap_migration.py."""
  15. from backend.app.core import db_dialect
  16. monkeypatch.setattr(db_dialect, "is_sqlite", lambda: True)
  17. monkeypatch.setattr(db_dialect, "is_postgres", lambda: False)
  18. # database.py imported is_sqlite at module load time — patch there too.
  19. from backend.app.core import database as database_module
  20. monkeypatch.setattr(database_module, "is_sqlite", lambda: True)
  21. @pytest.mark.asyncio
  22. async def test_billing_run_columns_and_legacy_archive_index_are_migrated(tmp_path):
  23. engine = create_async_engine(f"sqlite+aiosqlite:///{tmp_path / 'billing-run.db'}")
  24. try:
  25. async with engine.begin() as conn:
  26. await conn.run_sync(Base.metadata.create_all)
  27. await run_migrations(conn)
  28. queue_columns = {row[1] for row in (await conn.execute(text("PRAGMA table_info(print_queue)"))).all()}
  29. archive_columns = {row[1] for row in (await conn.execute(text("PRAGMA table_info(print_archives)"))).all()}
  30. notification_columns = {
  31. row[1] for row in (await conn.execute(text("PRAGMA table_info(notification_providers)"))).all()
  32. }
  33. archive_index_sql = await conn.scalar(
  34. text("SELECT sql FROM sqlite_master WHERE type = 'index' AND name = 'uq_wallet_transactions_archive'")
  35. )
  36. assert "billing_run_id" in queue_columns
  37. assert "billing_run_id" in archive_columns
  38. assert "on_billing_charge_failed" in notification_columns
  39. assert archive_index_sql is not None
  40. assert "WHERE print_run_id IS NULL" in archive_index_sql
  41. finally:
  42. await engine.dispose()