M5: use DB-merged runtime settings for MQTT/discovery; add MQTT test button; move Expose panel into config accordion
This commit is contained in:
+61
-17
@@ -340,7 +340,7 @@ def test_publish_discovery_sends_retained_for_enabled_entity(disco_db) -> None:
|
||||
mock_mgr.publish.side_effect = _capture
|
||||
|
||||
with (
|
||||
patch("app.services.ha_discovery._get_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.build_runtime_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.mqtt_manager", mock_mgr),
|
||||
):
|
||||
from app.services.ha_discovery import publish_discovery
|
||||
@@ -383,7 +383,7 @@ def test_publish_discovery_sends_empty_payload_for_disabled_entity(disco_db) ->
|
||||
mock_mgr.publish.side_effect = _capture
|
||||
|
||||
with (
|
||||
patch("app.services.ha_discovery._get_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.build_runtime_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.mqtt_manager", mock_mgr),
|
||||
):
|
||||
from app.services.ha_discovery import publish_discovery
|
||||
@@ -425,7 +425,7 @@ def test_publish_discovery_enabled_vs_disabled_payload(disco_db) -> None:
|
||||
mock_mgr.publish.side_effect = _capture
|
||||
|
||||
with (
|
||||
patch("app.services.ha_discovery._get_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.build_runtime_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.mqtt_manager", mock_mgr),
|
||||
):
|
||||
from app.services.ha_discovery import publish_discovery
|
||||
@@ -501,7 +501,7 @@ def test_publish_states_calls_value_getter_for_enabled_sensor() -> None:
|
||||
mock_catalog = [CatalogEntry(entity=mock_entity, enabled=True)]
|
||||
|
||||
with (
|
||||
patch("app.services.ha_discovery._get_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.build_runtime_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.mqtt_manager", mock_mgr),
|
||||
patch("app.services.ha_discovery.build_catalog", return_value=mock_catalog),
|
||||
):
|
||||
@@ -549,7 +549,7 @@ def test_publish_states_skips_disabled_entities() -> None:
|
||||
mock_catalog = [CatalogEntry(entity=mock_entity, enabled=False)]
|
||||
|
||||
with (
|
||||
patch("app.services.ha_discovery._get_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.build_runtime_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.mqtt_manager", mock_mgr),
|
||||
patch("app.services.ha_discovery.build_catalog", return_value=mock_catalog),
|
||||
):
|
||||
@@ -614,7 +614,7 @@ def test_publish_device_state_pushes_availability_and_state() -> None:
|
||||
mock_device.last_poll_ok = True
|
||||
|
||||
with (
|
||||
patch("app.services.ha_discovery._get_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.build_runtime_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.mqtt_manager", mock_mgr),
|
||||
patch("app.services.ha_discovery.build_catalog", return_value=mock_catalog),
|
||||
):
|
||||
@@ -658,7 +658,7 @@ def test_publish_device_state_publishes_offline_on_failed_poll() -> None:
|
||||
mock_device.last_poll_ok = False
|
||||
|
||||
with (
|
||||
patch("app.services.ha_discovery._get_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.build_runtime_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.mqtt_manager", mock_mgr),
|
||||
patch("app.services.ha_discovery.build_catalog", return_value=[]),
|
||||
):
|
||||
@@ -695,12 +695,13 @@ def test_publish_device_offline_publishes_offline_topic() -> None:
|
||||
mock_mgr.publish.side_effect = _capture
|
||||
|
||||
with (
|
||||
patch("app.services.ha_discovery._get_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.build_runtime_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.mqtt_manager", mock_mgr),
|
||||
):
|
||||
from app.services.ha_discovery import publish_device_offline
|
||||
from unittest.mock import MagicMock as _MagicMock
|
||||
|
||||
publish_device_offline(uuid_val)
|
||||
publish_device_offline(_MagicMock(), uuid_val)
|
||||
|
||||
assert len(published) == 1
|
||||
topic, payload = published[0]
|
||||
@@ -713,13 +714,55 @@ def test_publish_device_offline_publishes_offline_topic() -> None:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_should_publish_true_when_db_enabled_bootstrap_disabled(disco_db) -> None:
|
||||
"""_should_publish must use DB-merged settings: if DB has mqtt_enabled=True and
|
||||
ha_discovery_enabled=True (bootstrap has both False), publish should proceed.
|
||||
|
||||
This is the core regression test for Issue 2: bare get_settings() returns bootstrap
|
||||
(False); build_runtime_settings returns DB-merged (True).
|
||||
"""
|
||||
from app.services.config_page import build_runtime_settings
|
||||
from app.services.ha_discovery import _should_publish
|
||||
from app.models.config import AppConfigEntry
|
||||
from datetime import UTC, datetime
|
||||
|
||||
now = datetime.now(UTC)
|
||||
|
||||
# Write MQTT + discovery enabled into app_config (DB level)
|
||||
with Session(disco_db) as session:
|
||||
session.add(AppConfigEntry(key="MQTT_ENABLED", value="true", updated_at=now))
|
||||
session.add(AppConfigEntry(key="MQTT_BROKER_HOST", value="broker.test", updated_at=now))
|
||||
session.add(AppConfigEntry(key="HA_DISCOVERY_ENABLED", value="true", updated_at=now))
|
||||
session.commit()
|
||||
|
||||
# Bootstrap has mqtt_enabled=False and ha_discovery_enabled=False (env defaults)
|
||||
from app.config import Settings
|
||||
bootstrap = Settings(_env_file=None, mqtt_enabled=False, ha_discovery_enabled=False,
|
||||
app_database_url=str(disco_db.url))
|
||||
|
||||
with Session(disco_db) as session:
|
||||
runtime = build_runtime_settings(session, bootstrap)
|
||||
|
||||
# Simulate a connected broker for the _should_publish check
|
||||
mock_mgr_connected = MagicMock()
|
||||
mock_mgr_connected.is_connected = True
|
||||
|
||||
with patch("app.services.ha_discovery.mqtt_manager", mock_mgr_connected):
|
||||
result = _should_publish(runtime)
|
||||
|
||||
assert result is True, (
|
||||
"_should_publish must return True when DB has mqtt_enabled=True + ha_discovery_enabled=True, "
|
||||
"even if bootstrap Settings has both False"
|
||||
)
|
||||
|
||||
|
||||
def test_publish_discovery_noop_when_mqtt_disabled() -> None:
|
||||
"""publish_discovery must be a no-op when mqtt_enabled=False."""
|
||||
settings = _make_settings(mqtt_enabled=False, ha_discovery_enabled=True)
|
||||
mock_mgr = _make_mock_manager(is_connected=False)
|
||||
|
||||
with (
|
||||
patch("app.services.ha_discovery._get_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.build_runtime_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.mqtt_manager", mock_mgr),
|
||||
):
|
||||
from app.services.ha_discovery import publish_discovery
|
||||
@@ -739,7 +782,7 @@ def test_publish_states_noop_when_not_connected() -> None:
|
||||
mock_mgr = _make_mock_manager(is_connected=False)
|
||||
|
||||
with (
|
||||
patch("app.services.ha_discovery._get_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.build_runtime_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.mqtt_manager", mock_mgr),
|
||||
):
|
||||
from app.services.ha_discovery import publish_states
|
||||
@@ -758,7 +801,7 @@ def test_publish_discovery_noop_when_ha_discovery_disabled() -> None:
|
||||
mock_mgr = _make_mock_manager(is_connected=True)
|
||||
|
||||
with (
|
||||
patch("app.services.ha_discovery._get_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.build_runtime_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.mqtt_manager", mock_mgr),
|
||||
):
|
||||
from app.services.ha_discovery import publish_discovery
|
||||
@@ -781,7 +824,7 @@ def test_publish_device_state_noop_when_mqtt_disabled() -> None:
|
||||
mock_device.last_poll_ok = True
|
||||
|
||||
with (
|
||||
patch("app.services.ha_discovery._get_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.build_runtime_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.mqtt_manager", mock_mgr),
|
||||
):
|
||||
from app.services.ha_discovery import publish_device_state
|
||||
@@ -800,12 +843,13 @@ def test_publish_device_offline_noop_when_mqtt_disabled() -> None:
|
||||
mock_mgr = _make_mock_manager(is_connected=False)
|
||||
|
||||
with (
|
||||
patch("app.services.ha_discovery._get_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.build_runtime_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.mqtt_manager", mock_mgr),
|
||||
):
|
||||
from app.services.ha_discovery import publish_device_offline
|
||||
from unittest.mock import MagicMock as _MagicMock
|
||||
|
||||
publish_device_offline("some-uuid") # must not raise
|
||||
publish_device_offline(_MagicMock(), "some-uuid") # must not raise
|
||||
|
||||
mock_mgr.publish.assert_not_called()
|
||||
|
||||
@@ -984,7 +1028,7 @@ def test_publish_discovery_uses_retain_true_for_enabled_entity() -> None:
|
||||
catalog = [CatalogEntry(entity=entity, enabled=True)]
|
||||
|
||||
with (
|
||||
patch("app.services.ha_discovery._get_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.build_runtime_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.mqtt_manager", mock_mgr),
|
||||
patch("app.services.ha_discovery.build_catalog", return_value=catalog),
|
||||
):
|
||||
@@ -1085,7 +1129,7 @@ def test_real_provider_value_getter_returns_latest_reading_value(disco_db) -> No
|
||||
mock_device.last_poll_ok = True
|
||||
|
||||
with (
|
||||
patch("app.services.ha_discovery._get_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.build_runtime_settings", return_value=settings),
|
||||
patch("app.services.ha_discovery.mqtt_manager", mock_mgr),
|
||||
# NOTE: build_catalog is NOT mocked — we use the real modbus provider.
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user