From 6ef53a2c5d68d6b31b7cdf17737ec75782753661 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Sat, 24 Oct 2020 03:57:16 +0200 Subject: [PATCH] Add config flow for One wire (#39321) * Add support for config_flow Add support for switches Add support for binary sensors * Add config flow strings * Update .coveragerc * black-formatting * fixes for isort and flake * fixes for pylint * fixes for isort * fixes for isort * fixes for config_flow * Add devices to Device Registry * Updated comments * fixes for flake8 * Updated comments * Updated comments * Implement async_unload_entry * remove binary_sensor and switch implementation (will move to new PR) * Update .coveragerc Co-authored-by: Chris Talkington * Update .coveragerc * Review config flow to store the configuration type * Add config_flow tests * Move CONF_NAMES to constants * Fix isort * Tweak to onewire logger * Tweak to onewire logger for sensor * Reset _LOGGER variable * Normalise header * Normalise header * Update to use references in config flow translations * Improve test coverage * fixes for isort * Update async_unload_entry * Update common strings * Update imports * Move connect attempt to executor * Update common strings * Remove OWFS from config_flow * Prevent duplicate config entries * Fix isort * Fix flake8 * Fix tests following removal of OWFS implementation * Fix flake8 * Adjust config from yaml to config-flow, and add ability to specify sysbus directory * Ajust unique_id for config entries * Fix test_config_flow * Fix invalid merge * Convert yaml to config_entry * Update sysbus tests * Tweaks to yaml import process, and add OWFS warning * Enable migration of OWFS platform config to config_entry * update the existing corresponding config entry on OWFS conversion * Remove CONFIG_SCHEMA * Move data_schema to constants * Remove log message * Remove duplicate warning * Update already_configured to use already_configured_device constant * Schedule get_entities on the executor * Update duplicate entry check for OWServer * Review TryCatch * Schedule os.path.isdir on the executor * rename owhost/owport * Update checks for empty * Fix incorrect patch * Move config_flow validation methods to new OneWireHub * Fix typo and pre-commit * Cleanup try/else * patch async_setup/async_setup_entry * cleanup implicit exit point * Fix invalid patch * cleanup implicit exit point * cleanup implicit exit point Co-authored-by: Chris Talkington --- homeassistant/components/onewire/__init__.py | 30 ++ .../components/onewire/config_flow.py | 195 ++++++++++ homeassistant/components/onewire/const.py | 1 + .../components/onewire/manifest.json | 1 + .../components/onewire/onewirehub.py | 35 ++ homeassistant/components/onewire/sensor.py | 53 +-- homeassistant/components/onewire/strings.json | 26 ++ .../components/onewire/translations/en.json | 26 ++ homeassistant/generated/config_flows.py | 1 + tests/components/onewire/__init__.py | 59 ++++ tests/components/onewire/test_config_flow.py | 333 ++++++++++++++++++ .../components/onewire/test_entity_sysbus.py | 15 +- tests/components/onewire/test_init.py | 23 ++ tests/components/onewire/test_sensor.py | 8 +- 14 files changed, 774 insertions(+), 32 deletions(-) create mode 100644 homeassistant/components/onewire/config_flow.py create mode 100644 homeassistant/components/onewire/onewirehub.py create mode 100644 homeassistant/components/onewire/strings.json create mode 100644 homeassistant/components/onewire/translations/en.json create mode 100644 tests/components/onewire/test_config_flow.py create mode 100644 tests/components/onewire/test_init.py diff --git a/homeassistant/components/onewire/__init__.py b/homeassistant/components/onewire/__init__.py index 21dc0c2ead3..e228856a7fd 100644 --- a/homeassistant/components/onewire/__init__.py +++ b/homeassistant/components/onewire/__init__.py @@ -1 +1,31 @@ """The 1-Wire component.""" +import asyncio + +from .const import SUPPORTED_PLATFORMS + + +async def async_setup(hass, config): + """Set up 1-Wire integrations.""" + return True + + +async def async_setup_entry(hass, config_entry): + """Set up a 1-Wire proxy for a config entry.""" + for component in SUPPORTED_PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, component) + ) + return True + + +async def async_unload_entry(hass, config_entry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(config_entry, component) + for component in SUPPORTED_PLATFORMS + ] + ) + ) + return unload_ok diff --git a/homeassistant/components/onewire/config_flow.py b/homeassistant/components/onewire/config_flow.py new file mode 100644 index 00000000000..7cd963c6996 --- /dev/null +++ b/homeassistant/components/onewire/config_flow.py @@ -0,0 +1,195 @@ +"""Config flow for 1-Wire component.""" +import voluptuous as vol + +from homeassistant import exceptions +from homeassistant.config_entries import CONN_CLASS_LOCAL_POLL, ConfigFlow +from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE +from homeassistant.helpers.typing import HomeAssistantType + +from .const import ( # pylint: disable=unused-import + CONF_MOUNT_DIR, + CONF_TYPE_OWFS, + CONF_TYPE_OWSERVER, + CONF_TYPE_SYSBUS, + DEFAULT_OWSERVER_HOST, + DEFAULT_OWSERVER_PORT, + DEFAULT_SYSBUS_MOUNT_DIR, + DOMAIN, +) +from .onewirehub import OneWireHub + +DATA_SCHEMA_USER = vol.Schema( + {vol.Required(CONF_TYPE): vol.In([CONF_TYPE_OWSERVER, CONF_TYPE_SYSBUS])} +) +DATA_SCHEMA_OWSERVER = vol.Schema( + { + vol.Required(CONF_HOST, default=DEFAULT_OWSERVER_HOST): str, + vol.Required(CONF_PORT, default=DEFAULT_OWSERVER_PORT): int, + } +) +DATA_SCHEMA_MOUNTDIR = vol.Schema( + { + vol.Required(CONF_MOUNT_DIR, default=DEFAULT_SYSBUS_MOUNT_DIR): str, + } +) + + +async def validate_input_owserver(hass: HomeAssistantType, data): + """Validate the user input allows us to connect. + + Data has the keys from DATA_SCHEMA_OWSERVER with values provided by the user. + """ + + hub = OneWireHub(hass) + + host = data[CONF_HOST] + port = data[CONF_PORT] + if not await hub.can_connect(host, port): + raise CannotConnect + + # Return info that you want to store in the config entry. + return {"title": host} + + +def is_duplicate_owserver_entry(hass: HomeAssistantType, user_input): + """Check existing entries for matching host and port.""" + for config_entry in hass.config_entries.async_entries(DOMAIN): + if ( + config_entry.data[CONF_TYPE] == CONF_TYPE_OWSERVER + and config_entry.data[CONF_HOST] == user_input[CONF_HOST] + and config_entry.data[CONF_PORT] == str(user_input[CONF_PORT]) + ): + return True + return False + + +async def validate_input_mount_dir(hass: HomeAssistantType, data): + """Validate the user input allows us to connect. + + Data has the keys from DATA_SCHEMA_MOUNTDIR with values provided by the user. + """ + hub = OneWireHub(hass) + + mount_dir = data[CONF_MOUNT_DIR] + if not await hub.is_valid_mount_dir(mount_dir): + raise InvalidPath + + # Return info that you want to store in the config entry. + return {"title": mount_dir} + + +class OneWireFlowHandler(ConfigFlow, domain=DOMAIN): + """Handle 1-Wire config flow.""" + + VERSION = 1 + CONNECTION_CLASS = CONN_CLASS_LOCAL_POLL + + def __init__(self): + """Initialize 1-Wire config flow.""" + self.onewire_config = {} + + async def async_step_user(self, user_input=None): + """Handle 1-Wire config flow start. + + Let user manually input configuration. + """ + errors = {} + if user_input is not None: + self.onewire_config.update(user_input) + if CONF_TYPE_OWSERVER == user_input[CONF_TYPE]: + return await self.async_step_owserver() + if CONF_TYPE_SYSBUS == user_input[CONF_TYPE]: + return await self.async_step_mount_dir() + + return self.async_show_form( + step_id="user", + data_schema=DATA_SCHEMA_USER, + errors=errors, + ) + + async def async_step_owserver(self, user_input=None): + """Handle OWServer configuration.""" + errors = {} + if user_input: + # Prevent duplicate entries + if is_duplicate_owserver_entry(self.hass, user_input): + return self.async_abort(reason="already_configured") + + self.onewire_config.update(user_input) + + try: + info = await validate_input_owserver(self.hass, user_input) + except CannotConnect: + errors["base"] = "cannot_connect" + else: + return self.async_create_entry( + title=info["title"], data=self.onewire_config + ) + + return self.async_show_form( + step_id="owserver", + data_schema=DATA_SCHEMA_OWSERVER, + errors=errors, + ) + + async def async_step_mount_dir(self, user_input=None): + """Handle SysBus configuration.""" + errors = {} + if user_input: + # Prevent duplicate entries + await self.async_set_unique_id( + f"{CONF_TYPE_SYSBUS}:{user_input[CONF_MOUNT_DIR]}" + ) + self._abort_if_unique_id_configured() + + self.onewire_config.update(user_input) + + try: + info = await validate_input_mount_dir(self.hass, user_input) + except InvalidPath: + errors["base"] = "invalid_path" + else: + return self.async_create_entry( + title=info["title"], data=self.onewire_config + ) + + return self.async_show_form( + step_id="mount_dir", + data_schema=DATA_SCHEMA_MOUNTDIR, + errors=errors, + ) + + async def async_step_import(self, platform_config): + """Handle import configuration from YAML.""" + # OWServer + if platform_config[CONF_TYPE] == CONF_TYPE_OWSERVER: + if CONF_PORT not in platform_config: + platform_config[CONF_PORT] = DEFAULT_OWSERVER_PORT + return await self.async_step_owserver(platform_config) + + # OWFS + if platform_config[CONF_TYPE] == CONF_TYPE_OWFS: # pragma: no cover + # This part of the implementation does not conform to policy regarding 3rd-party libraries, and will not longer be updated. + # https://developers.home-assistant.io/docs/creating_platform_code_review/#5-communication-with-devicesservices + await self.async_set_unique_id( + f"{CONF_TYPE_OWFS}:{platform_config[CONF_MOUNT_DIR]}" + ) + self._abort_if_unique_id_configured( + updates=platform_config, reload_on_update=True + ) + return self.async_create_entry( + title=platform_config[CONF_MOUNT_DIR], data=platform_config + ) + + # SysBus + if CONF_MOUNT_DIR not in platform_config: + platform_config[CONF_MOUNT_DIR] = DEFAULT_SYSBUS_MOUNT_DIR + return await self.async_step_mount_dir(platform_config) + + +class CannotConnect(exceptions.HomeAssistantError): + """Error to indicate we cannot connect.""" + + +class InvalidPath(exceptions.HomeAssistantError): + """Error to indicate the path is invalid.""" diff --git a/homeassistant/components/onewire/const.py b/homeassistant/components/onewire/const.py index 83ffa12706c..eade0d11358 100644 --- a/homeassistant/components/onewire/const.py +++ b/homeassistant/components/onewire/const.py @@ -8,6 +8,7 @@ CONF_TYPE_OWFS = "OWFS" CONF_TYPE_OWSERVER = "OWServer" CONF_TYPE_SYSBUS = "SysBus" +DEFAULT_OWSERVER_HOST = "localhost" DEFAULT_OWSERVER_PORT = 4304 DEFAULT_SYSBUS_MOUNT_DIR = "/sys/bus/w1/devices/" diff --git a/homeassistant/components/onewire/manifest.json b/homeassistant/components/onewire/manifest.json index 42ef7e54603..47ab6ad2404 100644 --- a/homeassistant/components/onewire/manifest.json +++ b/homeassistant/components/onewire/manifest.json @@ -2,6 +2,7 @@ "domain": "onewire", "name": "1-Wire", "documentation": "https://www.home-assistant.io/integrations/onewire", + "config_flow": true, "requirements": ["pyownet==0.10.0.post1", "pi1wire==0.1.0"], "codeowners": ["@garbled1", "@epenet"] } diff --git a/homeassistant/components/onewire/onewirehub.py b/homeassistant/components/onewire/onewirehub.py new file mode 100644 index 00000000000..22bb0098f92 --- /dev/null +++ b/homeassistant/components/onewire/onewirehub.py @@ -0,0 +1,35 @@ +"""Hub for communication with 1-Wire server or mount_dir.""" +import logging +import os + +from pyownet import protocol + +from homeassistant.helpers.typing import HomeAssistantType + +_LOGGER = logging.getLogger(__name__) + + +class OneWireHub: + """Hub to communicate with SysBus or OWServer.""" + + def __init__(self, hass: HomeAssistantType): + """Initialize.""" + self.hass = hass + + async def can_connect(self, host, port) -> bool: + """Test if we can authenticate with the host.""" + try: + await self.hass.async_add_executor_job(protocol.proxy, host, port) + except (protocol.Error, protocol.ConnError) as exc: + _LOGGER.error( + "Cannot connect to owserver on %s:%d, got: %s", host, port, exc + ) + return False + return True + + async def is_valid_mount_dir(self, mount_dir) -> bool: + """Test that the mount_dir is a valid path.""" + if not await self.hass.async_add_executor_job(os.path.isdir, mount_dir): + _LOGGER.error("Cannot find directory %s", mount_dir) + return False + return True diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py index bce17dbf3cb..7952e5ca7de 100644 --- a/homeassistant/components/onewire/sensor.py +++ b/homeassistant/components/onewire/sensor.py @@ -8,9 +8,11 @@ from pyownet import protocol import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( CONF_HOST, CONF_PORT, + CONF_TYPE, ELECTRICAL_CURRENT_AMPERE, LIGHT_LUX, PERCENTAGE, @@ -29,6 +31,7 @@ from .const import ( CONF_TYPE_SYSBUS, DEFAULT_OWSERVER_PORT, DEFAULT_SYSBUS_MOUNT_DIR, + DOMAIN, PRESSURE_CBAR, ) @@ -125,36 +128,44 @@ def hb_info_from_type(dev_type="std"): return HOBBYBOARD_EF -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Old way of setting up 1-Wire platform.""" + if config.get(CONF_HOST): + config[CONF_TYPE] = CONF_TYPE_OWSERVER + elif config[CONF_MOUNT_DIR] == DEFAULT_SYSBUS_MOUNT_DIR: + config[CONF_TYPE] = CONF_TYPE_SYSBUS + else: # pragma: no cover + # This part of the implementation does not conform to policy regarding 3rd-party libraries, and will not longer be updated. + # https://developers.home-assistant.io/docs/creating_platform_code_review/#5-communication-with-devicesservices + config[CONF_TYPE] = CONF_TYPE_OWFS + + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=config + ) + ) + + +async def async_setup_entry(hass, config_entry, async_add_entities): """Set up 1-Wire platform.""" - entities = get_entities(config) - add_entities(entities, True) + entities = await hass.async_add_executor_job(get_entities, config_entry.data) + async_add_entities(entities, True) def get_entities(config): """Get a list of entities.""" - base_dir = config[CONF_MOUNT_DIR] - owhost = config.get(CONF_HOST) - owport = config[CONF_PORT] - - # Ensure type is configured - if owhost: - conf_type = CONF_TYPE_OWSERVER - elif base_dir == DEFAULT_SYSBUS_MOUNT_DIR: - conf_type = CONF_TYPE_SYSBUS - else: # pragma: no cover - # This part of the implementation does not conform to policy regarding 3rd-party libraries, and will not longer be updated. - # https://developers.home-assistant.io/docs/creating_platform_code_review/#5-communication-with-devicesservices - conf_type = CONF_TYPE_OWFS - entities = [] device_names = {} if CONF_NAMES in config: if isinstance(config[CONF_NAMES], dict): device_names = config[CONF_NAMES] + conf_type = config[CONF_TYPE] # We have an owserver on a remote(or local) host/port if conf_type == CONF_TYPE_OWSERVER: + owhost = config[CONF_HOST] + owport = config[CONF_PORT] + _LOGGER.debug("Initializing using %s:%s", owhost, owport) try: owproxy = protocol.proxy(host=owhost, port=owport) @@ -163,7 +174,7 @@ def get_entities(config): _LOGGER.error( "Cannot connect to owserver on %s:%d, got: %s", owhost, owport, exc ) - devices = [] + return entities for device in devices: _LOGGER.debug("Found device: %s", device) family = owproxy.read(f"{device}family").decode() @@ -200,8 +211,9 @@ def get_entities(config): # We have a raw GPIO ow sensor on a Pi elif conf_type == CONF_TYPE_SYSBUS: - _LOGGER.debug("Initializing using SysBus") - for p1sensor in Pi1Wire().find_all_sensors(): + base_dir = config[CONF_MOUNT_DIR] + _LOGGER.debug("Initializing using SysBus %s", base_dir) + for p1sensor in Pi1Wire(base_dir).find_all_sensors(): family = p1sensor.mac_address[:2] sensor_id = f"{family}-{p1sensor.mac_address[2:]}" if family not in DEVICE_SUPPORT_SYSBUS: @@ -232,6 +244,7 @@ def get_entities(config): else: # pragma: no cover # This part of the implementation does not conform to policy regarding 3rd-party libraries, and will not longer be updated. # https://developers.home-assistant.io/docs/creating_platform_code_review/#5-communication-with-devicesservices + base_dir = config[CONF_MOUNT_DIR] _LOGGER.debug("Initializing using OWFS %s", base_dir) _LOGGER.warning( "The OWFS implementation of 1-Wire sensors is deprecated, " diff --git a/homeassistant/components/onewire/strings.json b/homeassistant/components/onewire/strings.json new file mode 100644 index 00000000000..928907b319a --- /dev/null +++ b/homeassistant/components/onewire/strings.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_path": "Directory not found." + }, + "step": { + "owserver": { + "data": { + "host": "[%key:common::config_flow::data::host%]", + "port": "[%key:common::config_flow::data::port%]" + }, + "title": "Set owserver details" + }, + "user": { + "data": { + "type": "Connection type" + }, + "title": "Set up 1-Wire" + } + } + } +} diff --git a/homeassistant/components/onewire/translations/en.json b/homeassistant/components/onewire/translations/en.json new file mode 100644 index 00000000000..ec71701bf98 --- /dev/null +++ b/homeassistant/components/onewire/translations/en.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Service is already configured" + }, + "error": { + "cannot_connect": "Unable to connect.", + "invalid_path": "Directory not found." + }, + "step": { + "owserver": { + "data": { + "host": "Host", + "port": "Port" + }, + "title": "Set owserver details" + }, + "user": { + "data": { + "type": "Connection type" + }, + "title": "Set up 1-Wire" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index bd1a1ed4e53..f94c76417b4 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -132,6 +132,7 @@ FLOWS = [ "nws", "nzbget", "omnilogic", + "onewire", "onvif", "opentherm_gw", "openuv", diff --git a/tests/components/onewire/__init__.py b/tests/components/onewire/__init__.py index e5de65bc71e..9d9f4148fb4 100644 --- a/tests/components/onewire/__init__.py +++ b/tests/components/onewire/__init__.py @@ -1 +1,60 @@ """Tests for 1-Wire integration.""" +from asynctest.mock import patch + +from homeassistant.components.onewire.const import ( + CONF_TYPE_OWSERVER, + CONF_TYPE_SYSBUS, + DEFAULT_SYSBUS_MOUNT_DIR, + DOMAIN, +) +from homeassistant.config_entries import CONN_CLASS_LOCAL_POLL +from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE + +from tests.common import MockConfigEntry + + +async def setup_onewire_sysbus_integration(hass): + """Create the 1-Wire integration.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + source="user", + data={ + CONF_TYPE: CONF_TYPE_SYSBUS, + }, + unique_id=f"{CONF_TYPE_SYSBUS}:{DEFAULT_SYSBUS_MOUNT_DIR}", + connection_class=CONN_CLASS_LOCAL_POLL, + options={}, + entry_id="1", + ) + config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + return config_entry + + +async def setup_onewire_owserver_integration(hass): + """Create the 1-Wire integration.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + source="user", + data={ + CONF_TYPE: CONF_TYPE_OWSERVER, + CONF_HOST: "1.2.3.4", + CONF_PORT: "1234", + }, + unique_id=f"{CONF_TYPE_OWSERVER}:1.2.3.4:1234", + connection_class=CONN_CLASS_LOCAL_POLL, + options={}, + entry_id="2", + ) + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.onewire.sensor.protocol.proxy", + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + return config_entry diff --git a/tests/components/onewire/test_config_flow.py b/tests/components/onewire/test_config_flow.py new file mode 100644 index 00000000000..dfb64a3846e --- /dev/null +++ b/tests/components/onewire/test_config_flow.py @@ -0,0 +1,333 @@ +"""Tests for 1-Wire config flow.""" +from pyownet import protocol + +from homeassistant.components.onewire.const import ( + CONF_MOUNT_DIR, + CONF_TYPE_OWSERVER, + CONF_TYPE_SYSBUS, + DEFAULT_OWSERVER_PORT, + DEFAULT_SYSBUS_MOUNT_DIR, + DOMAIN, +) +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE +from homeassistant.data_entry_flow import ( + RESULT_TYPE_ABORT, + RESULT_TYPE_CREATE_ENTRY, + RESULT_TYPE_FORM, +) + +from . import setup_onewire_owserver_integration, setup_onewire_sysbus_integration + +from tests.async_mock import patch + + +async def test_user_owserver(hass): + """Test OWServer user flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert result["type"] == RESULT_TYPE_FORM + assert not result["errors"] + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_TYPE: CONF_TYPE_OWSERVER}, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "owserver" + assert not result["errors"] + + # Invalid server + with patch( + "homeassistant.components.onewire.onewirehub.protocol.proxy", + side_effect=protocol.ConnError, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_HOST: "1.2.3.4", CONF_PORT: 1234}, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "owserver" + assert result["errors"] == {"base": "cannot_connect"} + + # Valid server + with patch("homeassistant.components.onewire.onewirehub.protocol.proxy",), patch( + "homeassistant.components.onewire.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.onewire.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_HOST: "1.2.3.4", CONF_PORT: 1234}, + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "1.2.3.4" + assert result["data"] == { + CONF_TYPE: CONF_TYPE_OWSERVER, + CONF_HOST: "1.2.3.4", + CONF_PORT: 1234, + } + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_user_owserver_duplicate(hass): + """Test OWServer flow.""" + with patch( + "homeassistant.components.onewire.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.onewire.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + await setup_onewire_owserver_integration(hass) + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert result["type"] == RESULT_TYPE_FORM + assert not result["errors"] + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_TYPE: CONF_TYPE_OWSERVER}, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "owserver" + assert not result["errors"] + + # Duplicate server + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_HOST: "1.2.3.4", CONF_PORT: 1234}, + ) + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_user_sysbus(hass): + """Test SysBus flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert result["type"] == RESULT_TYPE_FORM + assert not result["errors"] + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_TYPE: CONF_TYPE_SYSBUS}, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "mount_dir" + assert not result["errors"] + + # Invalid path + with patch( + "homeassistant.components.onewire.onewirehub.os.path.isdir", + return_value=False, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_MOUNT_DIR: "/sys/bus/invalid_directory"}, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "mount_dir" + assert result["errors"] == {"base": "invalid_path"} + + # Valid path + with patch( + "homeassistant.components.onewire.onewirehub.os.path.isdir", + return_value=True, + ), patch( + "homeassistant.components.onewire.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.onewire.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_MOUNT_DIR: "/sys/bus/directory"}, + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "/sys/bus/directory" + assert result["data"] == { + CONF_TYPE: CONF_TYPE_SYSBUS, + CONF_MOUNT_DIR: "/sys/bus/directory", + } + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_user_sysbus_duplicate(hass): + """Test SysBus duplicate flow.""" + with patch( + "homeassistant.components.onewire.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.onewire.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + await setup_onewire_sysbus_integration(hass) + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert result["type"] == RESULT_TYPE_FORM + assert not result["errors"] + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_TYPE: CONF_TYPE_SYSBUS}, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "mount_dir" + assert not result["errors"] + + # Valid path + with patch( + "homeassistant.components.onewire.onewirehub.os.path.isdir", + return_value=True, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_MOUNT_DIR: DEFAULT_SYSBUS_MOUNT_DIR}, + ) + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_import_sysbus(hass): + """Test import step.""" + + with patch( + "homeassistant.components.onewire.onewirehub.os.path.isdir", + return_value=True, + ), patch( + "homeassistant.components.onewire.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.onewire.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={CONF_TYPE: CONF_TYPE_SYSBUS}, + ) + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == DEFAULT_SYSBUS_MOUNT_DIR + assert result["data"] == { + CONF_TYPE: CONF_TYPE_SYSBUS, + CONF_MOUNT_DIR: DEFAULT_SYSBUS_MOUNT_DIR, + } + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_import_sysbus_with_mount_dir(hass): + """Test import step.""" + + with patch( + "homeassistant.components.onewire.onewirehub.os.path.isdir", + return_value=True, + ), patch( + "homeassistant.components.onewire.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.onewire.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={ + CONF_TYPE: CONF_TYPE_SYSBUS, + CONF_MOUNT_DIR: DEFAULT_SYSBUS_MOUNT_DIR, + }, + ) + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == DEFAULT_SYSBUS_MOUNT_DIR + assert result["data"] == { + CONF_TYPE: CONF_TYPE_SYSBUS, + CONF_MOUNT_DIR: DEFAULT_SYSBUS_MOUNT_DIR, + } + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_import_owserver(hass): + """Test import step.""" + + with patch("homeassistant.components.onewire.onewirehub.protocol.proxy",), patch( + "homeassistant.components.onewire.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.onewire.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={ + CONF_TYPE: CONF_TYPE_OWSERVER, + CONF_HOST: "1.2.3.4", + }, + ) + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "1.2.3.4" + assert result["data"] == { + CONF_TYPE: CONF_TYPE_OWSERVER, + CONF_HOST: "1.2.3.4", + CONF_PORT: DEFAULT_OWSERVER_PORT, + } + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_import_owserver_with_port(hass): + """Test import step.""" + + with patch("homeassistant.components.onewire.onewirehub.protocol.proxy",), patch( + "homeassistant.components.onewire.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.onewire.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={ + CONF_TYPE: CONF_TYPE_OWSERVER, + CONF_HOST: "1.2.3.4", + CONF_PORT: "1234", + }, + ) + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "1.2.3.4" + assert result["data"] == { + CONF_TYPE: CONF_TYPE_OWSERVER, + CONF_HOST: "1.2.3.4", + CONF_PORT: "1234", + } + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 diff --git a/tests/components/onewire/test_entity_sysbus.py b/tests/components/onewire/test_entity_sysbus.py index 8a233315ab2..ba6f5fb62c2 100644 --- a/tests/components/onewire/test_entity_sysbus.py +++ b/tests/components/onewire/test_entity_sysbus.py @@ -1,5 +1,5 @@ """Tests for 1-Wire devices connected on SysBus.""" -from unittest.mock import PropertyMock, patch +from unittest.mock import patch from pi1wire import InvalidCRCException, UnsupportResponseException import pytest @@ -93,19 +93,18 @@ async def test_onewiredirect_setup_valid_device(hass, device_id): """Test that sysbus config entry works correctly.""" entity_registry = mock_registry(hass) + glob_result = [f"/{DEFAULT_SYSBUS_MOUNT_DIR}/{device_id}"] read_side_effect = [] expected_sensors = MOCK_DEVICE_SENSORS[device_id]["sensors"] for expected_sensor in expected_sensors: read_side_effect.append(expected_sensor["injected_value"]) with patch( - "homeassistant.components.onewire.sensor.Pi1Wire" - ) as mock_pi1wire, patch("pi1wire.OneWire") as mock_owsensor: - type(mock_owsensor).mac_address = PropertyMock( - return_value=device_id.replace("-", "") - ) - mock_owsensor.get_temperature.side_effect = read_side_effect - mock_pi1wire.return_value.find_all_sensors.return_value = [mock_owsensor] + "homeassistant.components.onewire.sensor.os.path.isdir", return_value=True + ), patch("pi1wire._finder.glob.glob", return_value=glob_result,), patch( + "pi1wire.OneWire.get_temperature", + side_effect=read_side_effect, + ): assert await async_setup_component(hass, SENSOR_DOMAIN, MOCK_CONFIG) await hass.async_block_till_done() diff --git a/tests/components/onewire/test_init.py b/tests/components/onewire/test_init.py new file mode 100644 index 00000000000..6c09d0a595f --- /dev/null +++ b/tests/components/onewire/test_init.py @@ -0,0 +1,23 @@ +"""Tests for 1-Wire config flow.""" +from homeassistant.components.onewire.const import DOMAIN +from homeassistant.config_entries import ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED + +from . import setup_onewire_owserver_integration, setup_onewire_sysbus_integration + + +async def test_unload_entry(hass): + """Test being able to unload an entry.""" + config_entry_owserver = await setup_onewire_owserver_integration(hass) + config_entry_sysbus = await setup_onewire_sysbus_integration(hass) + + assert len(hass.config_entries.async_entries(DOMAIN)) == 2 + assert config_entry_owserver.state == ENTRY_STATE_LOADED + assert config_entry_sysbus.state == ENTRY_STATE_LOADED + + assert await hass.config_entries.async_unload(config_entry_owserver.entry_id) + assert await hass.config_entries.async_unload(config_entry_sysbus.entry_id) + await hass.async_block_till_done() + + assert config_entry_owserver.state == ENTRY_STATE_NOT_LOADED + assert config_entry_sysbus.state == ENTRY_STATE_NOT_LOADED + assert not hass.data.get(DOMAIN) diff --git a/tests/components/onewire/test_sensor.py b/tests/components/onewire/test_sensor.py index 0aa02a9906d..751ef106147 100644 --- a/tests/components/onewire/test_sensor.py +++ b/tests/components/onewire/test_sensor.py @@ -7,7 +7,7 @@ from tests.common import assert_setup_component async def test_setup_minimum(hass): - """Test setup with minimum configuration.""" + """Test old platform setup with minimum configuration.""" config = {"sensor": {"platform": "onewire"}} with assert_setup_component(1, "sensor"): assert await async_setup_component(hass, sensor.DOMAIN, config) @@ -15,7 +15,7 @@ async def test_setup_minimum(hass): async def test_setup_sysbus(hass): - """Test setup with SysBus configuration.""" + """Test old platform setup with SysBus configuration.""" config = { "sensor": { "platform": "onewire", @@ -28,7 +28,7 @@ async def test_setup_sysbus(hass): async def test_setup_owserver(hass): - """Test setup with OWServer configuration.""" + """Test old platform setup with OWServer configuration.""" config = {"sensor": {"platform": "onewire", "host": "localhost"}} with assert_setup_component(1, "sensor"): assert await async_setup_component(hass, sensor.DOMAIN, config) @@ -36,7 +36,7 @@ async def test_setup_owserver(hass): async def test_setup_owserver_with_port(hass): - """Test setup with OWServer configuration.""" + """Test old platform setup with OWServer configuration.""" config = {"sensor": {"platform": "onewire", "host": "localhost", "port": "1234"}} with assert_setup_component(1, "sensor"): assert await async_setup_component(hass, sensor.DOMAIN, config)