Skip to content

Commit

Permalink
Add unique ID to config entry in Luftdaten (#62176)
Browse files Browse the repository at this point in the history
  • Loading branch information
frenck committed Dec 19, 2021
1 parent b559d88 commit 7fe895e
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 97 deletions.
26 changes: 4 additions & 22 deletions homeassistant/components/luftdaten/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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__)
Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand Down
29 changes: 5 additions & 24 deletions homeassistant/components/luftdaten/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand All @@ -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:
Expand All @@ -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
)
32 changes: 32 additions & 0 deletions tests/components/luftdaten/conftest.py
Original file line number Diff line number Diff line change
@@ -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
132 changes: 81 additions & 51 deletions tests/components/luftdaten/test_config_flow.py
Original file line number Diff line number Diff line change
@@ -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,
}

0 comments on commit 7fe895e

Please sign in to comment.