Add unique ID to config entry in Luftdaten (#62176)

This commit is contained in:
Franck Nijhof 2021-12-19 12:42:52 +01:00 committed by GitHub
parent b559d8845e
commit 7fe895e554
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 122 additions and 97 deletions

View file

@ -7,7 +7,6 @@ from luftdaten import Luftdaten
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 @@ from homeassistant.const import (
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 @@ SENSOR_KEYS: list[str] = [desc.key for desc in SENSOR_TYPES]
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(

View file

@ -18,25 +18,6 @@ import homeassistant.helpers.config_validation as cv
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 @@ class LuftDatenFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
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 @@ class LuftDatenFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
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
)

View file

@ -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

View file

@ -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)
MockConfigEntry(domain=DOMAIN, data=conf).add_to_hass(hass)
flow = config_flow.LuftDatenFlowHandler()
flow.hass = hass
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
result = await flow.async_step_user(user_input=conf)
assert result["errors"] == {CONF_SENSOR_ID: "already_configured"}
assert result.get("type") == RESULT_TYPE_FORM
assert result.get("step_id") == SOURCE_USER
assert "flow_id" in result
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_SENSOR_ID: 12345},
)
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"}
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: "invalid_sensor"}
async def test_show_form(hass):
"""Test that the form is served with no input."""
flow = config_flow.LuftDatenFlowHandler()
flow.hass = hass
result = await flow.async_step_user(user_input=None)
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)
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_SENSOR_ID: 12345,
CONF_SHOW_ON_MAP: False,
},
)
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,
}
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,
}