From 1bd94bb805e2febec858bcc0c715f651ab02e64b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edvard=20Filistovi=C4=8D?= Date: Thu, 6 Aug 2026 00:41:07 +0300 Subject: [PATCH] [bluetooth_proxy] Scanner-state sync in set_mode; platform-gate tests (#18100) --- .../bluetooth_proxy/bluetooth_proxy.cpp | 4 ++ .../test_outer_schema_mirror.py | 5 +- .../bluetooth_proxy/test_platform_gates.py | 48 +++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 tests/component_tests/bluetooth_proxy/test_platform_gates.py diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index ad2fc094ae..e681030611 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -561,6 +561,10 @@ void BluetoothProxy::bluetooth_scanner_set_mode(bool active) { } } if (this->api_connection_ != nullptr) { + // Keep loop()'s change detector in step with the state sent here, so a + // failed restart (scan_running_ dropped by the tracker) is not reported + // twice — once now and again on the next tick. + this->last_scan_running_ = this->hub_->scan_running(); this->send_bluetooth_scanner_state_(); } } diff --git a/tests/component_tests/bluetooth_proxy/test_outer_schema_mirror.py b/tests/component_tests/bluetooth_proxy/test_outer_schema_mirror.py index b7e1736f72..17a05a67b9 100644 --- a/tests/component_tests/bluetooth_proxy/test_outer_schema_mirror.py +++ b/tests/component_tests/bluetooth_proxy/test_outer_schema_mirror.py @@ -2,9 +2,10 @@ them without importing the esp32 BLE stack; pin the two declarations together. The outer schema carries no defaults (the per-platform schema applies them), so -drift cannot surface in validation output — a key renamed or re-bounded in +drift cannot surface in validation output — a key renamed or removed in _esp32_config_schema() but not here would silently vanish from the dashboard's -field extractor. This test is what catches that. +field extractor. This test is what catches that; validator bounds are pinned +separately only for connection_slots (test_idf_max_connections_mirror). """ import voluptuous as vol diff --git a/tests/component_tests/bluetooth_proxy/test_platform_gates.py b/tests/component_tests/bluetooth_proxy/test_platform_gates.py new file mode 100644 index 0000000000..4d7997fbce --- /dev/null +++ b/tests/component_tests/bluetooth_proxy/test_platform_gates.py @@ -0,0 +1,48 @@ +"""The three platform-gate branches: BLE-less platforms are rejected with the +real reason, hub platforms reject GATT-only options by name, and the +advertisement-only arm applies its own defaults.""" + +import pytest + +from esphome import config_validation as cv +from esphome.components import bluetooth_proxy +from esphome.const import CONF_ACTIVE, KEY_TARGET_PLATFORM +from esphome.core import CORE, KEY_CORE + + +def _set_platform(platform: str) -> None: + CORE.data.setdefault(KEY_CORE, {})[KEY_TARGET_PLATFORM] = platform + + +def test_ble_less_platform_gets_the_real_reason() -> None: + _set_platform("esp8266") + with pytest.raises(cv.Invalid, match="not supported on esp8266"): + bluetooth_proxy.CONFIG_SCHEMA({}) + + +def test_ble_less_platform_connection_keys_fall_through() -> None: + # The key-level rejection must not fire here — it would imply an + # advertisement-only proxy exists on this platform. + _set_platform("esp8266") + with pytest.raises(cv.Invalid, match="not supported on esp8266"): + bluetooth_proxy.CONFIG_SCHEMA({"connection_slots": 2}) + + +def test_hub_platform_rejects_active() -> None: + _set_platform("ln882x") + with pytest.raises(cv.Invalid, match="Active connections are not supported"): + bluetooth_proxy.CONFIG_SCHEMA({"active": True}) + + +def test_hub_platform_rejects_connection_keys_by_name() -> None: + _set_platform("ln882x") + with pytest.raises(cv.Invalid, match="'connection_slots' requires active"): + bluetooth_proxy.CONFIG_SCHEMA({"connection_slots": 2}) + with pytest.raises(cv.Invalid, match="'cache_services' requires active"): + bluetooth_proxy.CONFIG_SCHEMA({"cache_services": True}) + + +def test_hub_platform_accepts_the_advertisement_only_shape() -> None: + _set_platform("ln882x") + validated = bluetooth_proxy.CONFIG_SCHEMA({}) + assert validated[CONF_ACTIVE] is False