"""Tests for WiFi component public helpers.""" import pytest from esphome.components.esp32 import const from esphome.components.wifi import ( check_placeholder_credentials, has_native_wifi, variant_has_wifi, ) from esphome.const import ( CONF_AP, CONF_NETWORKS, CONF_SSID, CONF_WIFI, PLACEHOLDER_WIFI_SSID, Platform, ) from esphome.core import EsphomeError, Lambda @pytest.mark.parametrize( "variant", [ # Upstream's canonical uppercase form. const.VARIANT_ESP32, const.VARIANT_ESP32S2, const.VARIANT_ESP32S3, const.VARIANT_ESP32C3, const.VARIANT_ESP32C6, # Lowercase form external callers (e.g. device-builder's # ``Esp32Variant`` StrEnum) surface. "esp32", "esp32s3", "esp32c3", # Mixed-case — defence in depth against future callers that # pull the value off some other serialisation. "Esp32", ], ) def test_variant_has_wifi_for_native_phy_variants(variant: str) -> None: """Variants with a native WiFi PHY → True, case-insensitive.""" assert variant_has_wifi(variant) is True @pytest.mark.parametrize( "variant", [ # Upstream's canonical uppercase form. const.VARIANT_ESP32H2, const.VARIANT_ESP32P4, # Lowercase form external callers (e.g. device-builder's # ``Esp32Variant`` StrEnum) surface. "esp32h2", "esp32p4", # Mixed-case — defence in depth against future callers that # pull the value off some other serialisation. "Esp32H2", ], ) def test_variant_has_wifi_for_no_phy_variants(variant: str) -> None: """Variants that need ``esp32_hosted`` → False, case-insensitive.""" assert variant_has_wifi(variant) is False def test_has_native_wifi_dispatches_esp32_to_variant_check() -> None: """ESP32 platform routes through ``variant_has_wifi``.""" assert ( has_native_wifi(platform=Platform.ESP32, variant=const.VARIANT_ESP32C3) is True ) assert ( has_native_wifi(platform=Platform.ESP32, variant=const.VARIANT_ESP32H2) is False ) def test_has_native_wifi_esp32_variant_case_insensitive() -> None: """has_native_wifi accepts lowercase variant input. External callers (device-builder's wizard, etc.) may surface variant strings from their own enums that don't match upstream's uppercase convention. The dispatcher should classify them identically. """ assert has_native_wifi(platform=Platform.ESP32, variant="esp32h2") is False assert has_native_wifi(platform=Platform.ESP32, variant="esp32c3") is True def test_has_native_wifi_dispatches_rp2040_to_board_check() -> None: """RP2040 platform routes through ``rp2040.board_id_has_wifi``.""" assert has_native_wifi(platform=Platform.RP2, board="rpipicow") is True assert has_native_wifi(platform=Platform.RP2, board="rpipico") is False def test_has_native_wifi_returns_false_for_nrf52() -> None: """nRF52 family is BLE-only — no Wi-Fi PHY in the platform.""" assert has_native_wifi(platform=Platform.NRF52) is False def test_has_native_wifi_returns_false_for_host() -> None: """``host`` platform compiles ESPHome to a host binary — no radio at all.""" assert has_native_wifi(platform=Platform.HOST) is False def test_has_native_wifi_returns_false_for_unknown_platform() -> None: """Unknown platform string fails closed. A future platform added to ESPHome that's missed here returns False rather than silently emitting a ``wifi:`` block external tooling would have to compile and reject — fail-closed surfaces the gap as an obvious "needs wifi support added" signal. """ assert has_native_wifi(platform="not-a-real-platform") is False @pytest.mark.parametrize( "platform", [ Platform.ESP8266, Platform.BK72XX, Platform.RTL87XX, Platform.LN882X, Platform.LIBRETINY_OLDSTYLE, ], ) def test_has_native_wifi_returns_true_for_wifi_first_platforms(platform: str) -> None: """Catch-all Wi-Fi-first platforms → True regardless of board / variant.""" assert has_native_wifi(platform=platform) is True def test_has_native_wifi_esp32_without_variant_assumes_wifi() -> None: """ESP32 without a variant id falls open to True (the chip family default).""" assert has_native_wifi(platform=Platform.ESP32) is True def test_has_native_wifi_rp2040_without_board_assumes_wifi() -> None: """RP2040 without a board id falls open to True (custom-board default).""" assert has_native_wifi(platform=Platform.RP2) is True def _wifi_config( *, networks: list[dict] | None = None, ap: dict | None = None, ) -> dict: """Build a minimal config dict matching the post-validation shape.""" wifi: dict = {} if networks is not None: wifi[CONF_NETWORKS] = networks if ap is not None: wifi[CONF_AP] = ap return {CONF_WIFI: wifi} def test_check_placeholder_credentials_passes_with_real_ssid() -> None: """A real SSID compiles without complaint.""" config = _wifi_config(networks=[{CONF_SSID: "home_network"}]) assert check_placeholder_credentials(config) is None def test_check_placeholder_credentials_refuses_placeholder_ssid() -> None: """The placeholder SSID is rejected with an actionable message.""" config = _wifi_config(networks=[{CONF_SSID: PLACEHOLDER_WIFI_SSID}]) with pytest.raises(EsphomeError) as exc_info: check_placeholder_credentials(config) message = str(exc_info.value) assert "wifi.networks[0].ssid" in message assert "secrets.yaml" in message def test_check_placeholder_credentials_refuses_placeholder_in_second_network() -> None: """Index reporting picks the placeholder out of a mixed network list.""" config = _wifi_config( networks=[ {CONF_SSID: "home_network"}, {CONF_SSID: PLACEHOLDER_WIFI_SSID}, ], ) with pytest.raises(EsphomeError) as exc_info: check_placeholder_credentials(config) assert "wifi.networks[1].ssid" in str(exc_info.value) def test_check_placeholder_credentials_refuses_placeholder_ap_ssid() -> None: """An AP using the placeholder broadcast name is also refused.""" config = _wifi_config(ap={CONF_SSID: PLACEHOLDER_WIFI_SSID}) with pytest.raises(EsphomeError) as exc_info: check_placeholder_credentials(config) assert "wifi.ap.ssid" in str(exc_info.value) def test_check_placeholder_credentials_no_wifi_passes() -> None: """Ethernet-only / wifi-less configs skip the check entirely.""" assert check_placeholder_credentials({}) is None def test_check_placeholder_credentials_skips_template_ssid() -> None: """A templated (Lambda) SSID is not a string and is skipped.""" config = _wifi_config(networks=[{CONF_SSID: Lambda('return "x";')}]) assert check_placeholder_credentials(config) is None