test_notification_provider_field_parity.py 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. """Every client-settable notification provider column must reach the API (#2945).
  2. The defect this pins is not a wrong value, it is a field that exists everywhere
  3. except the modules a toggle has to cross. `on_stock_reorder_alert` and
  4. `on_stock_break_alert` had model columns, service producers, templates, UI
  5. toggles and a frontend test asserting the PATCH — and no schema field, so
  6. Pydantic dropped them, the PATCH answered 200, and nothing was written. #1184
  7. introduced that gap and every layer it did touch worked, which is why it went
  8. unnoticed for months.
  9. These are structural checks of structural facts: a column that is absent from a
  10. schema cannot be sent at all, whatever the routes do with it afterwards. The
  11. behaviour behind them — create, both read routes and PATCH, over the same
  12. derived column list — is covered through the API in
  13. `backend/tests/integration/test_notifications_api.py`.
  14. The two schemas here are the whole of it. The update route needs nothing
  15. beyond `NotificationProviderUpdate`, because it applies changes with a generic
  16. `model_dump(exclude_unset=True)` + `setattr` loop rather than a third
  17. hand-maintained map: once the field survives the schema, it is written.
  18. """
  19. from __future__ import annotations
  20. import pytest
  21. from backend.app.models.notification import NotificationProvider
  22. from backend.app.schemas.notification import (
  23. NotificationProviderCreate,
  24. NotificationProviderUpdate,
  25. )
  26. from backend.tests._fixtures.notification_toggles import (
  27. EVENT_TOGGLE_COLUMNS,
  28. SETTABLE_COLUMNS,
  29. TOGGLE_TARGET,
  30. )
  31. def test_there_are_event_columns_to_check() -> None:
  32. """Guard the guard: an empty enumeration would make every test below vacuous."""
  33. assert len(EVENT_TOGGLE_COLUMNS) > 20
  34. def test_the_targets_cover_both_directions() -> None:
  35. """Guard the other guard, the one the integration round-trips lean on.
  36. ``TOGGLE_TARGET`` exists so a test drives each toggle to whatever its
  37. default is not. If every column defaulted the same way -- or if the default
  38. lookup quietly started returning one constant -- the targets would collapse
  39. to a single value and the round-trips would be answered by the default again
  40. without anything failing. Both values have to appear.
  41. """
  42. assert set(TOGGLE_TARGET.values()) == {True, False}
  43. @pytest.mark.parametrize("column", EVENT_TOGGLE_COLUMNS)
  44. def test_every_event_column_has_a_python_default(column: str) -> None:
  45. """``TOGGLE_TARGET`` derives each target from the Python-side default, so a
  46. toggle that only has a ``server_default`` would get the wrong target and
  47. its round trip would pass on the default alone. Fail here instead."""
  48. assert NotificationProvider.__table__.columns[column].default is not None
  49. @pytest.mark.parametrize("column", SETTABLE_COLUMNS)
  50. def test_every_settable_column_is_settable_on_create(column: str) -> None:
  51. """Absent from the Create schema, the field is silently dropped from the POST."""
  52. assert column in NotificationProviderCreate.model_fields
  53. @pytest.mark.parametrize("column", SETTABLE_COLUMNS)
  54. def test_every_settable_column_is_settable_on_update(column: str) -> None:
  55. """This is the one the report hit: the PATCH succeeds and writes nothing."""
  56. assert column in NotificationProviderUpdate.model_fields