diff --git a/homeassistant/components/luftdaten/__init__.py b/homeassistant/components/luftdaten/__init__.py index e449507b1943ab..a2d7631552d7dc 100644 --- a/homeassistant/components/luftdaten/__init__.py +++ b/homeassistant/components/luftdaten/__init__.py @@ -7,7 +7,6 @@ from luftdaten.exceptions import LuftdatenError from homeassistant.components.sensor import SensorDeviceClass, SensorEntityDescription -from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONF_MONITORED_CONDITIONS, @@ -18,13 +17,11 @@ TEMP_CELSIUS, Platform, ) -from homeassistant.core import callback from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_track_time_interval -from .config_flow import duplicate_stations from .const import CONF_SENSOR_ID, DEFAULT_SCAN_INTERVAL, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -83,13 +80,6 @@ CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False) -@callback -def _async_fixup_sensor_id(hass, config_entry, sensor_id): - hass.config_entries.async_update_entry( - config_entry, data={**config_entry.data, CONF_SENSOR_ID: int(sensor_id)} - ) - - async def async_setup_entry(hass, config_entry): """Set up Luftdaten as config entry.""" hass.data.setdefault( @@ -100,19 +90,11 @@ async def async_setup_entry(hass, config_entry): }, ) - if not isinstance(config_entry.data[CONF_SENSOR_ID], int): - _async_fixup_sensor_id(hass, config_entry, config_entry.data[CONF_SENSOR_ID]) - - if ( - config_entry.data[CONF_SENSOR_ID] in duplicate_stations(hass) - and config_entry.source == SOURCE_IMPORT - ): - _LOGGER.warning( - "Removing duplicate sensors for station %s", - config_entry.data[CONF_SENSOR_ID], + # For backwards compat, set unique ID + if config_entry.unique_id is None: + hass.config_entries.async_update_entry( + config_entry, unique_id=config_entry.data[CONF_SENSOR_ID] ) - hass.async_create_task(hass.config_entries.async_remove(config_entry.entry_id)) - return False try: luftdaten = LuftDatenData( diff --git a/homeassistant/components/luftdaten/config_flow.py b/homeassistant/components/luftdaten/config_flow.py index b7aae271815164..febc42e28fd3b9 100644 --- a/homeassistant/components/luftdaten/config_flow.py +++ b/homeassistant/components/luftdaten/config_flow.py @@ -18,25 +18,6 @@ from .const import CONF_SENSOR_ID, DEFAULT_SCAN_INTERVAL, DOMAIN -@callback -def configured_sensors(hass): - """Return a set of configured Luftdaten sensors.""" - return { - entry.data[CONF_SENSOR_ID] - for entry in hass.config_entries.async_entries(DOMAIN) - } - - -@callback -def duplicate_stations(hass): - """Return a set of duplicate configured Luftdaten stations.""" - stations = [ - int(entry.data[CONF_SENSOR_ID]) - for entry in hass.config_entries.async_entries(DOMAIN) - ] - return {x for x in stations if stations.count(x) > 1} - - class LuftDatenFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle a Luftdaten config flow.""" @@ -59,10 +40,8 @@ async def async_step_user(self, user_input=None): if not user_input: return self._show_form() - sensor_id = user_input[CONF_SENSOR_ID] - - if sensor_id in configured_sensors(self.hass): - return self._show_form({CONF_SENSOR_ID: "already_configured"}) + await self.async_set_unique_id(str(user_input[CONF_SENSOR_ID])) + self._abort_if_unique_id_configured() luftdaten = Luftdaten(user_input[CONF_SENSOR_ID]) try: @@ -86,4 +65,6 @@ async def async_step_user(self, user_input=None): scan_interval = user_input.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) user_input.update({CONF_SCAN_INTERVAL: scan_interval.total_seconds()}) - return self.async_create_entry(title=str(sensor_id), data=user_input) + return self.async_create_entry( + title=str(user_input[CONF_SENSOR_ID]), data=user_input + ) diff --git a/tests/components/luftdaten/conftest.py b/tests/components/luftdaten/conftest.py new file mode 100644 index 00000000000000..28cade2c34dfcb --- /dev/null +++ b/tests/components/luftdaten/conftest.py @@ -0,0 +1,32 @@ +"""Fixtures for Luftdaten tests.""" +from __future__ import annotations + +from collections.abc import Generator +from unittest.mock import patch + +import pytest + +from homeassistant.components.luftdaten import DOMAIN +from homeassistant.components.luftdaten.const import CONF_SENSOR_ID + +from tests.common import MockConfigEntry + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Return the default mocked config entry.""" + return MockConfigEntry( + title="12345", + domain=DOMAIN, + data={CONF_SENSOR_ID: 123456}, + unique_id="12345", + ) + + +@pytest.fixture +def mock_setup_entry() -> Generator[None, None, None]: + """Mock setting up a config entry.""" + with patch( + "homeassistant.components.luftdaten.async_setup_entry", return_value=True + ): + yield diff --git a/tests/components/luftdaten/test_config_flow.py b/tests/components/luftdaten/test_config_flow.py index ac475a8ab38997..9b9aa139e025b4 100644 --- a/tests/components/luftdaten/test_config_flow.py +++ b/tests/components/luftdaten/test_config_flow.py @@ -1,84 +1,114 @@ """Define tests for the Luftdaten config flow.""" -from datetime import timedelta -from unittest.mock import patch +from unittest.mock import MagicMock, patch -from homeassistant import data_entry_flow -from homeassistant.components.luftdaten import DOMAIN, config_flow +from luftdaten.exceptions import LuftdatenConnectionError + +from homeassistant.components.luftdaten import DOMAIN from homeassistant.components.luftdaten.const import CONF_SENSOR_ID +from homeassistant.config_entries import SOURCE_USER from homeassistant.const import CONF_SCAN_INTERVAL, CONF_SHOW_ON_MAP +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import ( + RESULT_TYPE_ABORT, + RESULT_TYPE_CREATE_ENTRY, + RESULT_TYPE_FORM, +) from tests.common import MockConfigEntry -async def test_duplicate_error(hass): +async def test_duplicate_error( + hass: HomeAssistant, mock_config_entry: MockConfigEntry +) -> None: """Test that errors are shown when duplicates are added.""" - conf = {CONF_SENSOR_ID: "12345abcde"} + mock_config_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + + assert result.get("type") == RESULT_TYPE_FORM + assert result.get("step_id") == SOURCE_USER + assert "flow_id" in result - MockConfigEntry(domain=DOMAIN, data=conf).add_to_hass(hass) - flow = config_flow.LuftDatenFlowHandler() - flow.hass = hass + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_SENSOR_ID: 12345}, + ) - result = await flow.async_step_user(user_input=conf) - assert result["errors"] == {CONF_SENSOR_ID: "already_configured"} + assert result2.get("type") == RESULT_TYPE_ABORT + assert result2.get("reason") == "already_configured" -async def test_communication_error(hass): +async def test_communication_error(hass: HomeAssistant) -> None: """Test that no sensor is added while unable to communicate with API.""" - conf = {CONF_SENSOR_ID: "12345abcde"} + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) - flow = config_flow.LuftDatenFlowHandler() - flow.hass = hass + assert result.get("type") == RESULT_TYPE_FORM + assert result.get("step_id") == SOURCE_USER + assert "flow_id" in result - with patch("luftdaten.Luftdaten.get_data", return_value=None): - result = await flow.async_step_user(user_input=conf) - assert result["errors"] == {CONF_SENSOR_ID: "invalid_sensor"} + with patch("luftdaten.Luftdaten.get_data", side_effect=LuftdatenConnectionError): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_SENSOR_ID: 12345}, + ) + assert result2.get("type") == RESULT_TYPE_FORM + assert result2.get("step_id") == SOURCE_USER + assert result2.get("errors") == {CONF_SENSOR_ID: "cannot_connect"} -async def test_invalid_sensor(hass): + +async def test_invalid_sensor(hass: HomeAssistant) -> None: """Test that an invalid sensor throws an error.""" - conf = {CONF_SENSOR_ID: "12345abcde"} + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) - flow = config_flow.LuftDatenFlowHandler() - flow.hass = hass + assert result.get("type") == RESULT_TYPE_FORM + assert result.get("step_id") == SOURCE_USER + assert "flow_id" in result with patch("luftdaten.Luftdaten.get_data", return_value=False), patch( "luftdaten.Luftdaten.validate_sensor", return_value=False ): - result = await flow.async_step_user(user_input=conf) - assert result["errors"] == {CONF_SENSOR_ID: "invalid_sensor"} - - -async def test_show_form(hass): - """Test that the form is served with no input.""" - flow = config_flow.LuftDatenFlowHandler() - flow.hass = hass + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_SENSOR_ID: 12345}, + ) - result = await flow.async_step_user(user_input=None) + assert result2.get("type") == RESULT_TYPE_FORM + assert result2.get("step_id") == SOURCE_USER + assert result2.get("errors") == {CONF_SENSOR_ID: "invalid_sensor"} - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "user" - -async def test_step_user(hass): +async def test_step_user(hass: HomeAssistant, mock_setup_entry: MagicMock) -> None: """Test that the user step works.""" - conf = { - CONF_SENSOR_ID: "12345abcde", - CONF_SHOW_ON_MAP: False, - CONF_SCAN_INTERVAL: timedelta(minutes=5), - } + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) - flow = config_flow.LuftDatenFlowHandler() - flow.hass = hass + assert result.get("type") == RESULT_TYPE_FORM + assert result.get("step_id") == SOURCE_USER + assert "flow_id" in result with patch("luftdaten.Luftdaten.get_data", return_value=True), patch( "luftdaten.Luftdaten.validate_sensor", return_value=True ): - result = await flow.async_step_user(user_input=conf) - - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "12345abcde" - assert result["data"] == { - CONF_SENSOR_ID: "12345abcde", - CONF_SHOW_ON_MAP: False, - CONF_SCAN_INTERVAL: 300, - } + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_SENSOR_ID: 12345, + CONF_SHOW_ON_MAP: False, + }, + ) + + assert result2.get("type") == RESULT_TYPE_CREATE_ENTRY + assert result2.get("title") == "12345" + assert result2.get("data") == { + CONF_SENSOR_ID: 12345, + CONF_SHOW_ON_MAP: False, + CONF_SCAN_INTERVAL: 600.0, + }