test_oidc_env_managed_migration.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. """The is_env_managed column has to reach databases that already exist (#2593).
  2. The model test covers a table freshly created from metadata, which is not how
  3. an upgrade arrives: an installed instance has an oidc_providers table without
  4. the column, and only run_migrations adds it there. Every boot re-runs the whole
  5. migration set, so adding it twice must be a no-op rather than an error.
  6. """
  7. from __future__ import annotations
  8. import pytest
  9. from sqlalchemy import text
  10. from sqlalchemy.ext.asyncio import create_async_engine
  11. from backend.app.core.database import Base, run_migrations
  12. def _register_all_models():
  13. from backend.app.models import ( # noqa: F401
  14. ams_history,
  15. ams_label,
  16. api_key,
  17. archive,
  18. color_catalog,
  19. external_link,
  20. filament,
  21. group,
  22. kprofile_note,
  23. library,
  24. maintenance,
  25. notification,
  26. notification_template,
  27. print_log,
  28. print_queue,
  29. printer,
  30. project,
  31. project_bom,
  32. settings,
  33. slot_preset,
  34. smart_plug,
  35. smart_plug_energy_snapshot,
  36. spool,
  37. spool_assignment,
  38. spool_catalog,
  39. spool_k_profile,
  40. spool_usage_history,
  41. spoolbuddy_device,
  42. user,
  43. user_email_pref,
  44. virtual_printer,
  45. )
  46. @pytest.fixture(autouse=True)
  47. def force_sqlite_dialect(monkeypatch):
  48. """Force the SQLite branch regardless of test env settings."""
  49. from backend.app.core import database as database_module, db_dialect
  50. monkeypatch.setattr(db_dialect, "is_sqlite", lambda: True)
  51. monkeypatch.setattr(db_dialect, "is_postgres", lambda: False)
  52. monkeypatch.setattr(database_module, "is_sqlite", lambda: True)
  53. @pytest.fixture
  54. async def engine():
  55. """A database as it stands before this change: every table created from the
  56. models, then the new column dropped again -- the model already declares it,
  57. so only removing it reproduces what an installed instance actually has."""
  58. _register_all_models()
  59. eng = create_async_engine("sqlite+aiosqlite:///:memory:", echo=False)
  60. async with eng.begin() as conn:
  61. await conn.run_sync(Base.metadata.create_all)
  62. await conn.execute(text("ALTER TABLE oidc_providers DROP COLUMN is_env_managed"))
  63. yield eng
  64. await eng.dispose()
  65. async def _columns(conn) -> set[str]:
  66. rows = await conn.execute(text("PRAGMA table_info(oidc_providers)"))
  67. return {r[1] for r in rows}
  68. @pytest.mark.asyncio
  69. async def test_migration_adds_the_column_to_an_existing_table(engine):
  70. async with engine.connect() as conn:
  71. assert "is_env_managed" not in await _columns(conn)
  72. async with engine.begin() as conn:
  73. await run_migrations(conn)
  74. async with engine.connect() as conn:
  75. assert "is_env_managed" in await _columns(conn)
  76. @pytest.mark.asyncio
  77. async def test_existing_rows_default_to_not_env_managed(engine):
  78. """A provider created through the UI before the upgrade must not come back
  79. locked -- is_env_managed decides whether the API refuses to edit it."""
  80. async with engine.begin() as conn:
  81. await conn.execute(
  82. text(
  83. "INSERT INTO oidc_providers"
  84. " (id, name, issuer_url, client_id, client_secret, scopes, is_enabled,"
  85. " auto_create_users, auto_link_existing_accounts, email_claim,"
  86. " require_email_verified)"
  87. " VALUES (1, 'UI provider', 'https://sso.example', 'app', 'enc',"
  88. " 'openid email profile', 1, 0, 0, 'email', 1)"
  89. )
  90. )
  91. await run_migrations(conn)
  92. async with engine.connect() as conn:
  93. row = await conn.execute(text("SELECT is_env_managed FROM oidc_providers WHERE id = 1"))
  94. assert not row.scalar()
  95. @pytest.mark.asyncio
  96. async def test_it_is_idempotent(engine):
  97. """Every boot re-runs the migration set."""
  98. for _ in range(2):
  99. async with engine.begin() as conn:
  100. await run_migrations(conn)
  101. async with engine.connect() as conn:
  102. assert "is_env_managed" in await _columns(conn)