diff --git a/homeassistant/components/airgradient/config_flow.py b/homeassistant/components/airgradient/config_flow.py index c7b617de272..fff2615365e 100644 --- a/homeassistant/components/airgradient/config_flow.py +++ b/homeassistant/components/airgradient/config_flow.py @@ -3,6 +3,8 @@ from typing import Any from airgradient import AirGradientClient, AirGradientError, ConfigurationControl +from awesomeversion import AwesomeVersion +from mashumaro import MissingField import voluptuous as vol from homeassistant.components import zeroconf @@ -12,6 +14,8 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN +MIN_VERSION = AwesomeVersion("3.1.1") + class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN): """AirGradient config flow.""" @@ -38,6 +42,9 @@ class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN): await self.async_set_unique_id(discovery_info.properties["serialno"]) self._abort_if_unique_id_configured(updates={CONF_HOST: host}) + if AwesomeVersion(discovery_info.properties["fw_ver"]) < MIN_VERSION: + return self.async_abort(reason="invalid_version") + session = async_get_clientsession(self.hass) self.client = AirGradientClient(host, session=session) await self.client.get_current_measures() @@ -78,6 +85,8 @@ class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN): current_measures = await self.client.get_current_measures() except AirGradientError: errors["base"] = "cannot_connect" + except MissingField: + return self.async_abort(reason="invalid_version") else: await self.async_set_unique_id(current_measures.serial_number) self._abort_if_unique_id_configured() diff --git a/homeassistant/components/airgradient/strings.json b/homeassistant/components/airgradient/strings.json index 9deaf17d0e4..3b1e9f9ee41 100644 --- a/homeassistant/components/airgradient/strings.json +++ b/homeassistant/components/airgradient/strings.json @@ -15,7 +15,8 @@ } }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "invalid_version": "This firmware version is unsupported. Please upgrade the firmware of the device to at least version 3.1.1." }, "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", diff --git a/tests/components/airgradient/conftest.py b/tests/components/airgradient/conftest.py index d2495c11a79..d5857fdc46a 100644 --- a/tests/components/airgradient/conftest.py +++ b/tests/components/airgradient/conftest.py @@ -62,7 +62,7 @@ def mock_new_airgradient_client( def mock_cloud_airgradient_client( mock_airgradient_client: AsyncMock, ) -> Generator[AsyncMock, None, None]: - """Mock a new AirGradient client.""" + """Mock a cloud AirGradient client.""" mock_airgradient_client.get_config.return_value = Config.from_json( load_fixture("get_config_cloud.json", DOMAIN) ) diff --git a/tests/components/airgradient/test_config_flow.py b/tests/components/airgradient/test_config_flow.py index 6bb951f2e26..217d2ac0e8c 100644 --- a/tests/components/airgradient/test_config_flow.py +++ b/tests/components/airgradient/test_config_flow.py @@ -4,6 +4,7 @@ from ipaddress import ip_address from unittest.mock import AsyncMock from airgradient import AirGradientConnectionError, ConfigurationControl +from mashumaro import MissingField from homeassistant.components.airgradient import DOMAIN from homeassistant.components.zeroconf import ZeroconfServiceInfo @@ -14,7 +15,7 @@ from homeassistant.data_entry_flow import FlowResultType from tests.common import MockConfigEntry -ZEROCONF_DISCOVERY = ZeroconfServiceInfo( +OLD_ZEROCONF_DISCOVERY = ZeroconfServiceInfo( ip_address=ip_address("10.0.0.131"), ip_addresses=[ip_address("10.0.0.131")], hostname="airgradient_84fce612f5b8.local.", @@ -29,6 +30,21 @@ ZEROCONF_DISCOVERY = ZeroconfServiceInfo( }, ) +ZEROCONF_DISCOVERY = ZeroconfServiceInfo( + ip_address=ip_address("10.0.0.131"), + ip_addresses=[ip_address("10.0.0.131")], + hostname="airgradient_84fce612f5b8.local.", + name="airgradient_84fce612f5b8._airgradient._tcp.local.", + port=80, + type="_airgradient._tcp.local.", + properties={ + "vendor": "AirGradient", + "fw_ver": "3.1.1", + "serialno": "84fce612f5b8", + "model": "I-9PSL", + }, +) + async def test_full_flow( hass: HomeAssistant, @@ -119,6 +135,34 @@ async def test_flow_errors( assert result["type"] is FlowResultType.CREATE_ENTRY +async def test_flow_old_firmware_version( + hass: HomeAssistant, + mock_airgradient_client: AsyncMock, + mock_setup_entry: AsyncMock, +) -> None: + """Test flow with old firmware version.""" + mock_airgradient_client.get_current_measures.side_effect = MissingField( + "", object, object + ) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + ) + await hass.async_block_till_done() + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: "10.0.0.131"}, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "invalid_version" + + async def test_duplicate( hass: HomeAssistant, mock_airgradient_client: AsyncMock, @@ -197,3 +241,14 @@ async def test_zeroconf_flow_cloud_device( ) assert result["type"] is FlowResultType.CREATE_ENTRY mock_cloud_airgradient_client.set_configuration_control.assert_not_called() + + +async def test_zeroconf_flow_abort_old_firmware(hass: HomeAssistant) -> None: + """Test zeroconf flow aborts with old firmware.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_ZEROCONF}, + data=OLD_ZEROCONF_DISCOVERY, + ) + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "invalid_version"