test_stock_alert_template_migration_2955.py 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. """The stock alert templates name the colour and subtype (#2955).
  2. The forecast groups by colour, so two colours of one product would otherwise send
  3. the same message. The migration rewrites a template body IF AND ONLY IF it is
  4. still the shipped default -- an admin who edited the wording keeps it, the same
  5. guard as the ha_sensor_alert rename (#2824).
  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 _STOCK_ALERT_TEMPLATE_BODIES, _migrate_stock_alert_template_sku_variables
  12. from backend.app.models.notification_template import DEFAULT_TEMPLATES
  13. @pytest.fixture
  14. async def engine():
  15. """In-memory SQLite with just the notification_templates table."""
  16. from backend.app.models.notification_template import NotificationTemplate
  17. engine = create_async_engine("sqlite+aiosqlite:///:memory:", echo=False)
  18. async with engine.begin() as conn:
  19. await conn.run_sync(NotificationTemplate.__table__.create)
  20. try:
  21. yield engine
  22. finally:
  23. await engine.dispose()
  24. async def _insert(conn, event_type: str, body: str) -> None:
  25. await conn.execute(
  26. text(
  27. "INSERT INTO notification_templates (event_type, name, title_template, body_template, is_default) "
  28. "VALUES (:et, 'n', 't', :b, 1)"
  29. ),
  30. {"et": event_type, "b": body},
  31. )
  32. async def _body(conn, event_type: str) -> str:
  33. return (
  34. await conn.execute(
  35. text("SELECT body_template FROM notification_templates WHERE event_type = :et"), {"et": event_type}
  36. )
  37. ).scalar_one()
  38. @pytest.mark.parametrize("event_type", sorted(_STOCK_ALERT_TEMPLATE_BODIES))
  39. async def test_rewrites_the_shipped_default(engine, event_type):
  40. old, _new = _STOCK_ALERT_TEMPLATE_BODIES[event_type]
  41. async with engine.begin() as conn:
  42. await _insert(conn, event_type, old)
  43. async with engine.begin() as conn:
  44. await _migrate_stock_alert_template_sku_variables(conn)
  45. body = await _body(conn, event_type)
  46. assert "{subtype}" in body
  47. assert "{color}" in body
  48. @pytest.mark.parametrize("event_type", sorted(_STOCK_ALERT_TEMPLATE_BODIES))
  49. async def test_leaves_an_edited_template_alone(engine, event_type):
  50. async with engine.begin() as conn:
  51. await _insert(conn, event_type, "Running low on {material}!")
  52. async with engine.begin() as conn:
  53. await _migrate_stock_alert_template_sku_variables(conn)
  54. assert await _body(conn, event_type) == "Running low on {material}!"
  55. @pytest.mark.parametrize("event_type", sorted(_STOCK_ALERT_TEMPLATE_BODIES))
  56. async def test_the_rewrite_is_exactly_what_a_fresh_install_gets(engine, event_type):
  57. """The migrated body and the DEFAULT_TEMPLATES body must not drift apart."""
  58. default = next(t for t in DEFAULT_TEMPLATES if t["event_type"] == event_type)
  59. assert _STOCK_ALERT_TEMPLATE_BODIES[event_type][1] == default["body_template"]
  60. async def test_running_it_twice_changes_nothing(engine):
  61. old, new = _STOCK_ALERT_TEMPLATE_BODIES["stock_reorder_alert"]
  62. async with engine.begin() as conn:
  63. await _insert(conn, "stock_reorder_alert", old)
  64. for _ in range(2):
  65. async with engine.begin() as conn:
  66. await _migrate_stock_alert_template_sku_variables(conn)
  67. async with engine.begin() as conn:
  68. assert await _body(conn, "stock_reorder_alert") == new