test_user_print_template_rename_migration.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. """Regression test for the user_print_* notification template rename migration (#1792).
  2. The four ``user_print_*`` notification templates seeded with names like
  3. "User Print Completed" looked indistinguishable from the provider-level
  4. "Print Completed" template in the Message Templates list (the EVENT_NAMES
  5. display map in routes/notification_templates.py already used the disambiguated
  6. "User Print Completed Email" label, but the seed wrote the short name to the
  7. DB, so the UI rendered the ambiguous one).
  8. The migration appends " Email" to those four template names IF AND ONLY IF
  9. the row still has the old default name — admins who renamed the template
  10. themselves keep their custom name. This test verifies both branches.
  11. """
  12. from __future__ import annotations
  13. import pytest
  14. from sqlalchemy import text
  15. from sqlalchemy.ext.asyncio import create_async_engine
  16. from backend.app.core.database import _migrate_rename_user_print_template_names
  17. @pytest.fixture
  18. async def engine():
  19. """In-memory SQLite with just the notification_templates table.
  20. The migration is a single UPDATE on one table, so the fixture only needs
  21. that table — avoids the brittleness of registering every model in the
  22. project just to satisfy run_migrations's broader DDL surface.
  23. """
  24. from backend.app.models.notification_template import NotificationTemplate
  25. engine = create_async_engine("sqlite+aiosqlite:///:memory:", echo=False)
  26. async with engine.begin() as conn:
  27. await conn.run_sync(NotificationTemplate.__table__.create)
  28. try:
  29. yield engine
  30. finally:
  31. await engine.dispose()
  32. _OLD_DEFAULTS = {
  33. "user_print_start": "User Print Started",
  34. "user_print_complete": "User Print Completed",
  35. "user_print_failed": "User Print Failed",
  36. "user_print_stopped": "User Print Stopped",
  37. }
  38. _NEW_DEFAULTS = {
  39. "user_print_start": "User Print Started Email",
  40. "user_print_complete": "User Print Completed Email",
  41. "user_print_failed": "User Print Failed Email",
  42. "user_print_stopped": "User Print Stopped Email",
  43. }
  44. async def _insert_template(conn, event_type: str, name: str) -> None:
  45. await conn.execute(
  46. text(
  47. "INSERT INTO notification_templates "
  48. "(event_type, name, title_template, body_template, is_default) "
  49. "VALUES (:et, :n, 't', 'b', 1)"
  50. ),
  51. {"et": event_type, "n": name},
  52. )
  53. async def _name_for(conn, event_type: str) -> str:
  54. return (
  55. await conn.execute(
  56. text("SELECT name FROM notification_templates WHERE event_type = :et"),
  57. {"et": event_type},
  58. )
  59. ).scalar_one()
  60. async def test_migration_renames_default_named_user_print_rows(engine):
  61. """Rows with the old default name get the new disambiguated name."""
  62. async with engine.begin() as conn:
  63. for event_type, old_name in _OLD_DEFAULTS.items():
  64. await _insert_template(conn, event_type, old_name)
  65. async with engine.begin() as conn:
  66. await _migrate_rename_user_print_template_names(conn)
  67. async with engine.begin() as conn:
  68. for event_type, new_name in _NEW_DEFAULTS.items():
  69. assert await _name_for(conn, event_type) == new_name
  70. async def test_migration_preserves_user_edited_names(engine):
  71. """An admin who renamed a template keeps their custom name across the migration."""
  72. async with engine.begin() as conn:
  73. await _insert_template(conn, "user_print_complete", "My Custom Renamed Template")
  74. await _insert_template(conn, "user_print_failed", "User Print Failed") # still default
  75. async with engine.begin() as conn:
  76. await _migrate_rename_user_print_template_names(conn)
  77. async with engine.begin() as conn:
  78. # Custom name preserved
  79. assert await _name_for(conn, "user_print_complete") == "My Custom Renamed Template"
  80. # Default name renamed
  81. assert await _name_for(conn, "user_print_failed") == "User Print Failed Email"
  82. async def test_migration_does_not_touch_provider_templates(engine):
  83. """The non-user provider templates with similar names must not be renamed."""
  84. async with engine.begin() as conn:
  85. await _insert_template(conn, "print_complete", "Print Completed")
  86. await _insert_template(conn, "print_failed", "Print Failed")
  87. async with engine.begin() as conn:
  88. await _migrate_rename_user_print_template_names(conn)
  89. async with engine.begin() as conn:
  90. assert await _name_for(conn, "print_complete") == "Print Completed"
  91. assert await _name_for(conn, "print_failed") == "Print Failed"
  92. async def test_migration_is_idempotent(engine):
  93. """Running the migration twice must not double-suffix already-renamed rows."""
  94. async with engine.begin() as conn:
  95. for event_type, old_name in _OLD_DEFAULTS.items():
  96. await _insert_template(conn, event_type, old_name)
  97. async with engine.begin() as conn:
  98. await _migrate_rename_user_print_template_names(conn)
  99. async with engine.begin() as conn:
  100. await _migrate_rename_user_print_template_names(conn)
  101. async with engine.begin() as conn:
  102. for event_type, new_name in _NEW_DEFAULTS.items():
  103. current = await _name_for(conn, event_type)
  104. assert current == new_name
  105. assert "Email Email" not in current
  106. async def test_migration_handles_empty_table(engine):
  107. """Migration on an empty table must be a safe no-op (fresh install path)."""
  108. async with engine.begin() as conn:
  109. await _migrate_rename_user_print_template_names(conn)
  110. async with engine.begin() as conn:
  111. count = (await conn.execute(text("SELECT COUNT(*) FROM notification_templates"))).scalar_one()
  112. assert count == 0