From 9ef86b7b66fab059eedd3c05a17abcd7c794ccba Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 23 Jan 2023 16:27:24 +0100 Subject: [PATCH] Add Thread integration (#86283) * Add Thread integration * Address review comments * Address review comments --- CODEOWNERS | 2 ++ homeassistant/components/otbr/manifest.json | 1 + homeassistant/components/thread/__init__.py | 30 +++++++++++++++++++ .../components/thread/config_flow.py | 19 ++++++++++++ homeassistant/components/thread/const.py | 3 ++ homeassistant/components/thread/manifest.json | 9 ++++++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/integrations.json | 6 ++++ tests/components/thread/__init__.py | 1 + tests/components/thread/conftest.py | 22 ++++++++++++++ tests/components/thread/test_config_flow.py | 29 ++++++++++++++++++ tests/components/thread/test_init.py | 29 ++++++++++++++++++ 12 files changed, 152 insertions(+) create mode 100644 homeassistant/components/thread/__init__.py create mode 100644 homeassistant/components/thread/config_flow.py create mode 100644 homeassistant/components/thread/const.py create mode 100644 homeassistant/components/thread/manifest.json create mode 100644 tests/components/thread/__init__.py create mode 100644 tests/components/thread/conftest.py create mode 100644 tests/components/thread/test_config_flow.py create mode 100644 tests/components/thread/test_init.py diff --git a/CODEOWNERS b/CODEOWNERS index 108775a8952..735445cd697 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1202,6 +1202,8 @@ build.json @home-assistant/supervisor /homeassistant/components/thermopro/ @bdraco /tests/components/thermopro/ @bdraco /homeassistant/components/thethingsnetwork/ @fabaff +/homeassistant/components/thread/ @home-assistant/core +/tests/components/thread/ @home-assistant/core /homeassistant/components/threshold/ @fabaff /tests/components/threshold/ @fabaff /homeassistant/components/tibber/ @danielhiversen diff --git a/homeassistant/components/otbr/manifest.json b/homeassistant/components/otbr/manifest.json index 50ca47c281e..7247899b80a 100644 --- a/homeassistant/components/otbr/manifest.json +++ b/homeassistant/components/otbr/manifest.json @@ -2,6 +2,7 @@ "domain": "otbr", "name": "Open Thread Border Router", "config_flow": true, + "dependencies": ["thread"], "documentation": "https://www.home-assistant.io/integrations/otbr", "requirements": ["python-otbr-api==1.0.1"], "after_dependencies": ["hassio"], diff --git a/homeassistant/components/thread/__init__.py b/homeassistant/components/thread/__init__.py new file mode 100644 index 00000000000..4da54e2c88a --- /dev/null +++ b/homeassistant/components/thread/__init__.py @@ -0,0 +1,30 @@ +"""The Thread integration.""" +from __future__ import annotations + +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.typing import ConfigType + +from .const import DOMAIN + + +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up the Thread integration.""" + if not hass.config_entries.async_entries(DOMAIN): + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT} + ) + ) + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up a config entry.""" + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + return True diff --git a/homeassistant/components/thread/config_flow.py b/homeassistant/components/thread/config_flow.py new file mode 100644 index 00000000000..978b4c10779 --- /dev/null +++ b/homeassistant/components/thread/config_flow.py @@ -0,0 +1,19 @@ +"""Config flow for the Thread integration.""" +from __future__ import annotations + +from homeassistant.config_entries import ConfigFlow +from homeassistant.data_entry_flow import FlowResult + +from .const import DOMAIN + + +class ThreadConfigFlow(ConfigFlow, domain=DOMAIN): + """Handle a config flow for Thread.""" + + VERSION = 1 + + async def async_step_import( + self, import_data: dict[str, str] | None = None + ) -> FlowResult: + """Set up by import from async_setup.""" + return self.async_create_entry(title="Thread", data={}) diff --git a/homeassistant/components/thread/const.py b/homeassistant/components/thread/const.py new file mode 100644 index 00000000000..e8fe950ca6d --- /dev/null +++ b/homeassistant/components/thread/const.py @@ -0,0 +1,3 @@ +"""Constants for the Thread integration.""" + +DOMAIN = "thread" diff --git a/homeassistant/components/thread/manifest.json b/homeassistant/components/thread/manifest.json new file mode 100644 index 00000000000..5eec75e6223 --- /dev/null +++ b/homeassistant/components/thread/manifest.json @@ -0,0 +1,9 @@ +{ + "domain": "thread", + "name": "Thread", + "codeowners": ["@home-assistant/core"], + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/thread", + "integration_type": "service", + "iot_class": "local_polling" +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 2e3c286629b..199fe2a3cf3 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -431,6 +431,7 @@ FLOWS = { "tesla_wall_connector", "thermobeacon", "thermopro", + "thread", "tibber", "tile", "tilt_ble", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 96e7568762c..0d79ab16dab 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -5571,6 +5571,12 @@ "config_flow": false, "iot_class": "local_polling" }, + "thread": { + "name": "Thread", + "integration_type": "service", + "config_flow": true, + "iot_class": "local_polling" + }, "tibber": { "name": "Tibber", "integration_type": "hub", diff --git a/tests/components/thread/__init__.py b/tests/components/thread/__init__.py new file mode 100644 index 00000000000..4643d876d9e --- /dev/null +++ b/tests/components/thread/__init__.py @@ -0,0 +1 @@ +"""Tests for the Thread integration.""" diff --git a/tests/components/thread/conftest.py b/tests/components/thread/conftest.py new file mode 100644 index 00000000000..37555d07a90 --- /dev/null +++ b/tests/components/thread/conftest.py @@ -0,0 +1,22 @@ +"""Test fixtures for the Thread integration.""" + +import pytest + +from homeassistant.components import thread + +from tests.common import MockConfigEntry + +CONFIG_ENTRY_DATA = {} + + +@pytest.fixture(name="thread_config_entry") +async def thread_config_entry_fixture(hass): + """Mock Thread config entry.""" + config_entry = MockConfigEntry( + data=CONFIG_ENTRY_DATA, + domain=thread.DOMAIN, + options={}, + title="Thread", + ) + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) diff --git a/tests/components/thread/test_config_flow.py b/tests/components/thread/test_config_flow.py new file mode 100644 index 00000000000..4b27144166b --- /dev/null +++ b/tests/components/thread/test_config_flow.py @@ -0,0 +1,29 @@ +"""Test the Thread config flow.""" +from unittest.mock import patch + +from homeassistant.components import thread +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResultType + + +async def test_import(hass: HomeAssistant) -> None: + """Test the import flow.""" + with patch( + "homeassistant.components.thread.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + thread.DOMAIN, context={"source": "import"} + ) + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "Thread" + assert result["data"] == {} + assert result["options"] == {} + assert len(mock_setup_entry.mock_calls) == 1 + + config_entry = hass.config_entries.async_entries(thread.DOMAIN)[0] + assert config_entry.data == {} + assert config_entry.options == {} + assert config_entry.title == "Thread" + assert config_entry.unique_id is None diff --git a/tests/components/thread/test_init.py b/tests/components/thread/test_init.py new file mode 100644 index 00000000000..c529f18d138 --- /dev/null +++ b/tests/components/thread/test_init.py @@ -0,0 +1,29 @@ +"""Test the Thread integration.""" + +from homeassistant.components import thread +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + + +async def test_create_entry(hass: HomeAssistant): + """Test an entry is created by async_setup.""" + assert len(hass.config_entries.async_entries(thread.DOMAIN)) == 0 + assert await async_setup_component(hass, thread.DOMAIN, {}) + await hass.async_block_till_done() + + assert len(hass.config_entries.async_entries(thread.DOMAIN)) == 1 + + +async def test_remove_entry(hass: HomeAssistant, thread_config_entry): + """Test removing the entry.""" + + config_entry = hass.config_entries.async_entries(thread.DOMAIN)[0] + assert await hass.config_entries.async_remove(config_entry.entry_id) == { + "require_restart": False + } + + +async def test_import_once(hass: HomeAssistant, thread_config_entry) -> None: + """Test only a single entry is created.""" + await hass.async_block_till_done() + assert len(hass.config_entries.async_entries(thread.DOMAIN)) == 1