Add authentication support to bsblan (#42306)
This commit is contained in:
parent
434cec7a88
commit
ba4d630470
6 changed files with 186 additions and 10 deletions
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
47
tests/components/bsblan/test_init.py
Normal file
47
tests/components/bsblan/test_init.py
Normal file
|
@ -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
|
Loading…
Add table
Reference in a new issue