Files
bambuddy/backend/tests/unit/test_notification_provider_field_parity.py

74 lines
3.2 KiB
Python

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