From b5300fb32ec4710bcb31dd5c0c744a3b7b4ba575 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 30 Dec 2021 22:42:46 +0100 Subject: [PATCH] Add configuration flow to Whois (#63069) Co-authored-by: Dave T <17680170+davet2001@users.noreply.github.com> --- .coveragerc | 1 + CODEOWNERS | 1 + homeassistant/components/whois/__init__.py | 17 +++- homeassistant/components/whois/config_flow.py | 52 ++++++++++++ homeassistant/components/whois/const.py | 3 + homeassistant/components/whois/manifest.json | 1 + homeassistant/components/whois/sensor.py | 45 ++++++++--- homeassistant/components/whois/strings.json | 14 ++++ .../components/whois/translations/en.json | 14 ++++ homeassistant/generated/config_flows.py | 1 + requirements_test_all.txt | 3 + tests/components/whois/__init__.py | 1 + tests/components/whois/conftest.py | 34 ++++++++ tests/components/whois/test_config_flow.py | 80 +++++++++++++++++++ 14 files changed, 254 insertions(+), 13 deletions(-) create mode 100644 homeassistant/components/whois/config_flow.py create mode 100644 homeassistant/components/whois/strings.json create mode 100644 homeassistant/components/whois/translations/en.json create mode 100644 tests/components/whois/__init__.py create mode 100644 tests/components/whois/conftest.py create mode 100644 tests/components/whois/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index b63c2cbaa1f..d0c15a448f7 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1268,6 +1268,7 @@ omit = homeassistant/components/waze_travel_time/helpers.py homeassistant/components/waze_travel_time/sensor.py homeassistant/components/webostv/* + homeassistant/components/whois/__init__.py homeassistant/components/whois/sensor.py homeassistant/components/wiffi/* homeassistant/components/wirelesstag/* diff --git a/CODEOWNERS b/CODEOWNERS index 2b9c92c5c09..72ec3ae2322 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1035,6 +1035,7 @@ tests/components/wemo/* @esev homeassistant/components/whirlpool/* @abmantis tests/components/whirlpool/* @abmantis homeassistant/components/whois/* @frenck +tests/components/whois/* @frenck homeassistant/components/wiffi/* @mampfes tests/components/wiffi/* @mampfes homeassistant/components/wilight/* @leofig-rj diff --git a/homeassistant/components/whois/__init__.py b/homeassistant/components/whois/__init__.py index 3f3ffefde48..520c9ec0bfe 100644 --- a/homeassistant/components/whois/__init__.py +++ b/homeassistant/components/whois/__init__.py @@ -1 +1,16 @@ -"""The whois component.""" +"""The Whois integration.""" +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import PLATFORMS + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up from a config entry.""" + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/whois/config_flow.py b/homeassistant/components/whois/config_flow.py new file mode 100644 index 00000000000..0f030a2b9b7 --- /dev/null +++ b/homeassistant/components/whois/config_flow.py @@ -0,0 +1,52 @@ +"""Config flow to configure the Whois integration.""" +from __future__ import annotations + +from typing import Any + +import voluptuous as vol + +from homeassistant.config_entries import ConfigFlow +from homeassistant.const import CONF_DOMAIN, CONF_NAME +from homeassistant.data_entry_flow import FlowResult + +from .const import DOMAIN + + +class WhoisFlowHandler(ConfigFlow, domain=DOMAIN): + """Config flow for Whois.""" + + VERSION = 1 + + imported_name: str | None = None + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle a flow initialized by the user.""" + if user_input is not None: + await self.async_set_unique_id(user_input[CONF_DOMAIN].lower()) + self._abort_if_unique_id_configured() + return self.async_create_entry( + title=self.imported_name or user_input[CONF_DOMAIN], + data={ + CONF_DOMAIN: user_input[CONF_DOMAIN].lower(), + }, + ) + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_DOMAIN): str, + } + ), + ) + + async def async_step_import(self, config: dict[str, Any]) -> FlowResult: + """Handle a flow initialized by importing a config.""" + self.imported_name = config[CONF_NAME] + return await self.async_step_user( + user_input={ + CONF_DOMAIN: config[CONF_DOMAIN], + } + ) diff --git a/homeassistant/components/whois/const.py b/homeassistant/components/whois/const.py index 7210ada91a2..b48fc3e1fee 100644 --- a/homeassistant/components/whois/const.py +++ b/homeassistant/components/whois/const.py @@ -4,7 +4,10 @@ from __future__ import annotations import logging from typing import Final +from homeassistant.const import Platform + DOMAIN: Final = "whois" +PLATFORMS = [Platform.SENSOR] LOGGER = logging.getLogger(__package__) diff --git a/homeassistant/components/whois/manifest.json b/homeassistant/components/whois/manifest.json index 6da78b32b7f..64922252565 100644 --- a/homeassistant/components/whois/manifest.json +++ b/homeassistant/components/whois/manifest.json @@ -3,6 +3,7 @@ "name": "Whois", "documentation": "https://www.home-assistant.io/integrations/whois", "requirements": ["python-whois==0.7.3"], + "config_flow": true, "codeowners": ["@frenck"], "iot_class": "cloud_polling" } diff --git a/homeassistant/components/whois/sensor.py b/homeassistant/components/whois/sensor.py index febdc68aa8c..2848e27e868 100644 --- a/homeassistant/components/whois/sensor.py +++ b/homeassistant/components/whois/sensor.py @@ -7,6 +7,7 @@ import voluptuous as vol import whois from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_DOMAIN, CONF_NAME, TIME_DAYS from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv @@ -19,10 +20,11 @@ from .const import ( ATTR_REGISTRAR, ATTR_UPDATED, DEFAULT_NAME, + DOMAIN, LOGGER, ) -SCANTERVAL = timedelta(hours=24) +SCAN_INTERVAL = timedelta(hours=24) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -39,21 +41,40 @@ def setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the WHOIS sensor.""" - domain = config[CONF_DOMAIN] - name = config[CONF_NAME] + LOGGER.warning( + "Configuration of the Whois platform in YAML is deprecated and will be " + "removed in Home Assistant 2022.4; Your existing configuration " + "has been imported into the UI automatically and can be safely removed " + "from your configuration.yaml file" + ) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={CONF_DOMAIN: config[CONF_DOMAIN], CONF_NAME: config[CONF_NAME]}, + ) + ) + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the platform from config_entry.""" + domain = entry.data[CONF_DOMAIN] try: - if "expiration_date" in whois.whois(domain): - add_entities([WhoisSensor(name, domain)], True) - else: - LOGGER.error( - "WHOIS lookup for %s didn't contain an expiration date", domain - ) - return + info = await hass.async_add_executor_job(whois.whois, domain) except whois.BaseException as ex: # pylint: disable=broad-except LOGGER.error("Exception %s occurred during WHOIS lookup for %s", ex, domain) return + if "expiration_date" not in info: + LOGGER.error("WHOIS lookup for %s didn't contain an expiration date", domain) + return + + async_add_entities([WhoisSensor(domain)], True) + class WhoisSensor(SensorEntity): """Implementation of a WHOIS sensor.""" @@ -61,11 +82,11 @@ class WhoisSensor(SensorEntity): _attr_icon = "mdi:calendar-clock" _attr_native_unit_of_measurement = TIME_DAYS - def __init__(self, name: str, domain: str) -> None: + def __init__(self, domain: str) -> None: """Initialize the sensor.""" + self._attr_name = domain self.whois = whois.whois self._domain = domain - self._attr_name = name def _empty_value_and_attributes(self) -> None: """Empty the state and attributes on an error.""" diff --git a/homeassistant/components/whois/strings.json b/homeassistant/components/whois/strings.json new file mode 100644 index 00000000000..bdd1375aee3 --- /dev/null +++ b/homeassistant/components/whois/strings.json @@ -0,0 +1,14 @@ +{ + "config": { + "step": { + "user": { + "data": { + "domain": "Domain name" + } + } + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" + } + } +} diff --git a/homeassistant/components/whois/translations/en.json b/homeassistant/components/whois/translations/en.json new file mode 100644 index 00000000000..8379ebe9579 --- /dev/null +++ b/homeassistant/components/whois/translations/en.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "Service is already configured" + }, + "step": { + "user": { + "data": { + "domain": "Domain name" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index c8888adbbd5..cd78d54a3bb 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -347,6 +347,7 @@ FLOWS = [ "waze_travel_time", "wemo", "whirlpool", + "whois", "wiffi", "wilight", "withings", diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 29862d79fec..251f39779ef 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1193,6 +1193,9 @@ python-tado==0.12.0 # homeassistant.components.twitch python-twitch-client==0.6.0 +# homeassistant.components.whois +python-whois==0.7.3 + # homeassistant.components.awair python_awair==0.2.1 diff --git a/tests/components/whois/__init__.py b/tests/components/whois/__init__.py new file mode 100644 index 00000000000..753779d0f40 --- /dev/null +++ b/tests/components/whois/__init__.py @@ -0,0 +1 @@ +"""Tests for the Whois integration.""" diff --git a/tests/components/whois/conftest.py b/tests/components/whois/conftest.py new file mode 100644 index 00000000000..ca9b123f491 --- /dev/null +++ b/tests/components/whois/conftest.py @@ -0,0 +1,34 @@ +"""Fixtures for Whois integration tests.""" +from __future__ import annotations + +from collections.abc import Generator +from unittest.mock import AsyncMock, patch + +import pytest + +from homeassistant.components.whois.const import DOMAIN +from homeassistant.const import CONF_DOMAIN + +from tests.common import MockConfigEntry + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Return the default mocked config entry.""" + return MockConfigEntry( + title="Home Assistant", + domain=DOMAIN, + data={ + CONF_DOMAIN: "Home-Assistant.io", + }, + unique_id="home-assistant.io", + ) + + +@pytest.fixture +def mock_setup_entry() -> Generator[AsyncMock, None, None]: + """Mock setting up a config entry.""" + with patch( + "homeassistant.components.whois.async_setup_entry", return_value=True + ) as mock_setup: + yield mock_setup diff --git a/tests/components/whois/test_config_flow.py b/tests/components/whois/test_config_flow.py new file mode 100644 index 00000000000..71cc195c407 --- /dev/null +++ b/tests/components/whois/test_config_flow.py @@ -0,0 +1,80 @@ +"""Tests for the Whois config flow.""" + +from unittest.mock import AsyncMock + +from homeassistant.components.whois.const import DOMAIN +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.const import CONF_DOMAIN, CONF_NAME +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_full_user_flow( + hass: HomeAssistant, + mock_setup_entry: AsyncMock, +) -> None: + """Test the full user configuration flow.""" + 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 + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_DOMAIN: "Example.com"}, + ) + + assert result2.get("type") == RESULT_TYPE_CREATE_ENTRY + assert result2.get("title") == "Example.com" + assert result2.get("data") == {CONF_DOMAIN: "example.com"} + + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_already_configured( + hass: HomeAssistant, + mock_setup_entry: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test we abort if already configured.""" + mock_config_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={CONF_DOMAIN: "HOME-Assistant.io"}, + ) + + assert result.get("type") == RESULT_TYPE_ABORT + assert result.get("reason") == "already_configured" + + assert len(mock_setup_entry.mock_calls) == 0 + + +async def test_import_flow( + hass: HomeAssistant, + mock_setup_entry: AsyncMock, +) -> None: + """Test the import configuration flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={CONF_DOMAIN: "Example.com", CONF_NAME: "My Example Domain"}, + ) + + assert result.get("type") == RESULT_TYPE_CREATE_ENTRY + assert result.get("title") == "My Example Domain" + assert result.get("data") == { + CONF_DOMAIN: "example.com", + } + + assert len(mock_setup_entry.mock_calls) == 1