diff --git a/homeassistant/components/bsblan/__init__.py b/homeassistant/components/bsblan/__init__.py index 9f4bb38e315..bab4af29422 100644 --- a/homeassistant/components/bsblan/__init__.py +++ b/homeassistant/components/bsblan/__init__.py @@ -5,7 +5,7 @@ from bsblan import BSBLan, BSBLanConnectionError from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -29,6 +29,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry.data[CONF_HOST], passkey=entry.data[CONF_PASSKEY], port=entry.data[CONF_PORT], + username=entry.data.get(CONF_USERNAME), + password=entry.data.get(CONF_PASSWORD), session=session, ) diff --git a/homeassistant/components/bsblan/config_flow.py b/homeassistant/components/bsblan/config_flow.py index faca81bb6a7..dee04e6ef85 100644 --- a/homeassistant/components/bsblan/config_flow.py +++ b/homeassistant/components/bsblan/config_flow.py @@ -6,7 +6,7 @@ from bsblan import BSBLan, BSBLanError, Info import voluptuous as vol from homeassistant.config_entries import CONN_CLASS_LOCAL_POLL, ConfigFlow -from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.typing import ConfigType @@ -37,6 +37,8 @@ class BSBLanFlowHandler(ConfigFlow, domain=DOMAIN): host=user_input[CONF_HOST], port=user_input[CONF_PORT], passkey=user_input.get(CONF_PASSKEY), + username=user_input.get(CONF_USERNAME), + password=user_input.get(CONF_PASSWORD), ) except BSBLanError: return self._show_setup_form({"base": "cannot_connect"}) @@ -52,6 +54,8 @@ class BSBLanFlowHandler(ConfigFlow, domain=DOMAIN): CONF_PORT: user_input[CONF_PORT], CONF_PASSKEY: user_input.get(CONF_PASSKEY), CONF_DEVICE_IDENT: info.device_identification, + CONF_USERNAME: user_input.get(CONF_USERNAME), + CONF_PASSWORD: user_input.get(CONF_PASSWORD), }, ) @@ -64,16 +68,30 @@ class BSBLanFlowHandler(ConfigFlow, domain=DOMAIN): vol.Required(CONF_HOST): str, vol.Optional(CONF_PORT, default=80): int, vol.Optional(CONF_PASSKEY): str, + vol.Optional(CONF_USERNAME): str, + vol.Optional(CONF_PASSWORD): str, } ), errors=errors or {}, ) async def _get_bsblan_info( - self, host: str, passkey: Optional[str], port: int + self, + host: str, + username: Optional[str], + password: Optional[str], + passkey: Optional[str], + port: int, ) -> Info: """Get device information from an BSBLan device.""" session = async_get_clientsession(self.hass) _LOGGER.debug("request bsblan.info:") - bsblan = BSBLan(host, passkey=passkey, port=port, session=session) + bsblan = BSBLan( + host, + username=username, + password=password, + passkey=passkey, + port=port, + session=session, + ) return await bsblan.info() diff --git a/homeassistant/components/bsblan/strings.json b/homeassistant/components/bsblan/strings.json index d9510808fc1..0bb084fb20d 100644 --- a/homeassistant/components/bsblan/strings.json +++ b/homeassistant/components/bsblan/strings.json @@ -8,7 +8,9 @@ "data": { "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]", - "passkey": "Passkey string" + "passkey": "Passkey string", + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" } } }, diff --git a/tests/components/bsblan/__init__.py b/tests/components/bsblan/__init__.py index 511b566ce41..f2e88d97ba2 100644 --- a/tests/components/bsblan/__init__.py +++ b/tests/components/bsblan/__init__.py @@ -5,7 +5,13 @@ from homeassistant.components.bsblan.const import ( CONF_PASSKEY, DOMAIN, ) -from homeassistant.const import CONF_HOST, CONF_PORT, CONTENT_TYPE_JSON +from homeassistant.const import ( + CONF_HOST, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, + CONTENT_TYPE_JSON, +) from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry, load_fixture @@ -26,6 +32,42 @@ async def init_integration( headers={"Content-Type": CONTENT_TYPE_JSON}, ) + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="RVS21.831F/127", + data={ + CONF_HOST: "example.local", + CONF_USERNAME: "nobody", + CONF_PASSWORD: "qwerty", + CONF_PASSKEY: "1234", + CONF_PORT: 80, + CONF_DEVICE_IDENT: "RVS21.831F/127", + }, + ) + + entry.add_to_hass(hass) + + if not skip_setup: + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + return entry + + +async def init_integration_without_auth( + hass: HomeAssistant, + aioclient_mock: AiohttpClientMocker, + skip_setup: bool = False, +) -> MockConfigEntry: + """Set up the BSBLan integration in Home Assistant.""" + + aioclient_mock.post( + "http://example.local:80/1234/JQ?Parameter=6224,6225,6226", + params={"Parameter": "6224,6225,6226"}, + text=load_fixture("bsblan/info.json"), + headers={"Content-Type": CONTENT_TYPE_JSON}, + ) + entry = MockConfigEntry( domain=DOMAIN, unique_id="RVS21.831F/127", diff --git a/tests/components/bsblan/test_config_flow.py b/tests/components/bsblan/test_config_flow.py index 4c04db012ba..38485fb7959 100644 --- a/tests/components/bsblan/test_config_flow.py +++ b/tests/components/bsblan/test_config_flow.py @@ -5,7 +5,13 @@ from homeassistant import data_entry_flow from homeassistant.components.bsblan import config_flow from homeassistant.components.bsblan.const import CONF_DEVICE_IDENT, CONF_PASSKEY from homeassistant.config_entries import SOURCE_USER -from homeassistant.const import CONF_HOST, CONF_PORT, CONTENT_TYPE_JSON +from homeassistant.const import ( + CONF_HOST, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, + CONTENT_TYPE_JSON, +) from homeassistant.core import HomeAssistant from . import init_integration @@ -37,7 +43,13 @@ async def test_connection_error( result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, context={"source": SOURCE_USER}, - data={CONF_HOST: "example.local", CONF_PASSKEY: "1234", CONF_PORT: 80}, + data={ + CONF_HOST: "example.local", + CONF_USERNAME: "nobody", + CONF_PASSWORD: "qwerty", + CONF_PASSKEY: "1234", + CONF_PORT: 80, + }, ) assert result["errors"] == {"base": "cannot_connect"} @@ -54,7 +66,13 @@ async def test_user_device_exists_abort( result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, context={"source": SOURCE_USER}, - data={CONF_HOST: "example.local", CONF_PASSKEY: "1234", CONF_PORT: 80}, + data={ + CONF_HOST: "example.local", + CONF_USERNAME: "nobody", + CONF_PASSWORD: "qwerty", + CONF_PASSKEY: "1234", + CONF_PORT: 80, + }, ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT @@ -80,10 +98,18 @@ async def test_full_user_flow_implementation( result = await hass.config_entries.flow.async_configure( result["flow_id"], - user_input={CONF_HOST: "example.local", CONF_PASSKEY: "1234", CONF_PORT: 80}, + user_input={ + CONF_HOST: "example.local", + CONF_USERNAME: "nobody", + CONF_PASSWORD: "qwerty", + CONF_PASSKEY: "1234", + CONF_PORT: 80, + }, ) assert result["data"][CONF_HOST] == "example.local" + assert result["data"][CONF_USERNAME] == "nobody" + assert result["data"][CONF_PASSWORD] == "qwerty" assert result["data"][CONF_PASSKEY] == "1234" assert result["data"][CONF_PORT] == 80 assert result["data"][CONF_DEVICE_IDENT] == "RVS21.831F/127" @@ -92,3 +118,42 @@ async def test_full_user_flow_implementation( entries = hass.config_entries.async_entries(config_flow.DOMAIN) assert entries[0].unique_id == "RVS21.831F/127" + + +async def test_full_user_flow_implementation_without_auth( + hass: HomeAssistant, aioclient_mock +) -> None: + """Test the full manual user flow from start to finish.""" + aioclient_mock.post( + "http://example2.local:80/JQ?Parameter=6224,6225,6226", + text=load_fixture("bsblan/info.json"), + headers={"Content-Type": CONTENT_TYPE_JSON}, + ) + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, + context={"source": SOURCE_USER}, + ) + + assert result["step_id"] == "user" + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_HOST: "example2.local", + CONF_PORT: 80, + }, + ) + + assert result["data"][CONF_HOST] == "example2.local" + assert result["data"][CONF_USERNAME] is None + assert result["data"][CONF_PASSWORD] is None + assert result["data"][CONF_PASSKEY] is None + assert result["data"][CONF_PORT] == 80 + assert result["data"][CONF_DEVICE_IDENT] == "RVS21.831F/127" + assert result["title"] == "RVS21.831F/127" + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + + entries = hass.config_entries.async_entries(config_flow.DOMAIN) + assert entries[0].unique_id == "RVS21.831F/127" diff --git a/tests/components/bsblan/test_init.py b/tests/components/bsblan/test_init.py new file mode 100644 index 00000000000..b6096ced0ac --- /dev/null +++ b/tests/components/bsblan/test_init.py @@ -0,0 +1,47 @@ +"""Tests for the BSBLan integration.""" +import aiohttp + +from homeassistant.components.bsblan.const import DOMAIN +from homeassistant.config_entries import ENTRY_STATE_SETUP_RETRY +from homeassistant.core import HomeAssistant + +from tests.components.bsblan import init_integration, init_integration_without_auth +from tests.test_util.aiohttp import AiohttpClientMocker + + +async def test_config_entry_not_ready( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the BSBLan configuration entry not ready.""" + aioclient_mock.post( + "http://example.local:80/1234/JQ?Parameter=6224,6225,6226", + exc=aiohttp.ClientError, + ) + + entry = await init_integration(hass, aioclient_mock) + assert entry.state == ENTRY_STATE_SETUP_RETRY + + +async def test_unload_config_entry( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the BSBLan configuration entry unloading.""" + entry = await init_integration(hass, aioclient_mock) + assert hass.data[DOMAIN] + + await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + assert not hass.data.get(DOMAIN) + + +async def test_config_entry_no_authentication( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the BSBLan configuration entry not ready.""" + aioclient_mock.post( + "http://example.local:80/1234/JQ?Parameter=6224,6225,6226", + exc=aiohttp.ClientError, + ) + + entry = await init_integration_without_auth(hass, aioclient_mock) + assert entry.state == ENTRY_STATE_SETUP_RETRY