Get kostal plenticore hostname id from get_settings (#93008)

* Get hostname id from get_settings

* Add try except in get_hostname_id function

* Update tests after adding get_hostname_id function

* Revert "Update tests after adding get_hostname_id function"

This reverts commit 5fa4e533cb18e8d141dbb1b7aed021f80f4312a2.

* Add test for G2 models in config flow.

* Add test for helper module.

* Fix test for numbers.

* Revert "Add try except in get_hostname_id function"

This reverts commit 059f5bd9b413ca060300e09a6a1f0dffb4420f56.

* Update variable name with known hostname ids to be private

---------

Co-authored-by: Stefan Gmeiner <stefangm42@gmail.com>
This commit is contained in:
erikbadman 2023-05-23 11:42:54 +02:00 committed by GitHub
parent 1dccb8a9a9
commit 2721874f13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 252 additions and 26 deletions

View File

@ -12,6 +12,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
from .helper import get_hostname_id
_LOGGER = logging.getLogger(__name__)
@ -32,9 +33,10 @@ async def test_connection(hass: HomeAssistant, data) -> str:
session = async_get_clientsession(hass)
async with ApiClient(session, data["host"]) as client:
await client.login(data["password"])
values = await client.get_setting_values("scb:network", "Hostname")
hostname_id = await get_hostname_id(client)
values = await client.get_setting_values("scb:network", hostname_id)
return values["scb:network"]["Hostname"]
return values["scb:network"][hostname_id]
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):

View File

@ -23,6 +23,7 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
_DataT = TypeVar("_DataT")
_KNOWN_HOSTNAME_IDS = ("Network:Hostname", "Hostname")
class Plenticore:
@ -69,6 +70,7 @@ class Plenticore:
)
# get some device meta data
hostname_id = await get_hostname_id(self._client)
settings = await self._client.get_setting_values(
{
"devices:local": [
@ -78,7 +80,7 @@ class Plenticore:
"Properties:VersionIOC",
"Properties:VersionMC",
],
"scb:network": ["Hostname"],
"scb:network": [hostname_id],
}
)
@ -91,7 +93,7 @@ class Plenticore:
identifiers={(DOMAIN, device_local["Properties:SerialNo"])},
manufacturer="Kostal",
model=f"{prod1} {prod2}",
name=settings["scb:network"]["Hostname"],
name=settings["scb:network"][hostname_id],
sw_version=f'IOC: {device_local["Properties:VersionIOC"]}'
+ f' MC: {device_local["Properties:VersionMC"]}',
)
@ -403,3 +405,12 @@ class PlenticoreDataFormatter:
return state
return PlenticoreDataFormatter.EM_STATES.get(value)
async def get_hostname_id(client: ApiClient) -> str:
"""Check for known existing hostname ids."""
all_settings = await client.get_settings()
for entry in all_settings["scb:network"]:
if entry.id in _KNOWN_HOSTNAME_IDS:
return entry.id
raise ApiException("Hostname identifier not found in KNOWN_HOSTNAME_IDS")

View File

@ -1,8 +1,10 @@
"""Test the Kostal Plenticore Solar Inverter config flow."""
import asyncio
from collections.abc import Generator
from unittest.mock import ANY, AsyncMock, MagicMock, patch
from pykoplenti import AuthenticationException
from pykoplenti import ApiClient, AuthenticationException, SettingsData
import pytest
from homeassistant import config_entries
from homeassistant.components.kostal_plenticore.const import DOMAIN
@ -11,8 +13,33 @@ from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
async def test_formx(hass: HomeAssistant) -> None:
"""Test we get the form."""
@pytest.fixture
def mock_apiclient() -> ApiClient:
"""Return a mocked ApiClient instance."""
apiclient = MagicMock(spec=ApiClient)
apiclient.__aenter__.return_value = apiclient
apiclient.__aexit__ = AsyncMock()
return apiclient
@pytest.fixture
def mock_apiclient_class(mock_apiclient) -> Generator[type[ApiClient], None, None]:
"""Return a mocked ApiClient class."""
with patch(
"homeassistant.components.kostal_plenticore.config_flow.ApiClient",
autospec=True,
) as mock_api_class:
mock_api_class.return_value = mock_apiclient
yield mock_api_class
async def test_form_g1(
hass: HomeAssistant,
mock_apiclient_class: type[ApiClient],
mock_apiclient: ApiClient,
) -> None:
"""Test the config flow for G1 models."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
@ -21,25 +48,19 @@ async def test_formx(hass: HomeAssistant) -> None:
assert result["errors"] == {}
with patch(
"homeassistant.components.kostal_plenticore.config_flow.ApiClient"
) as mock_api_class, patch(
"homeassistant.components.kostal_plenticore.async_setup_entry",
return_value=True,
) as mock_setup_entry:
# mock of the context manager instance
mock_api_ctx = MagicMock()
mock_api_ctx.login = AsyncMock()
mock_api_ctx.get_setting_values = AsyncMock(
mock_apiclient.login = AsyncMock()
mock_apiclient.get_settings = AsyncMock(
return_value={"scb:network": [SettingsData({"id": "Hostname"})]}
)
mock_apiclient.get_setting_values = AsyncMock(
# G1 model has the entry id "Hostname"
return_value={"scb:network": {"Hostname": "scb"}}
)
# mock of the return instance of ApiClient
mock_api = MagicMock()
mock_api.__aenter__.return_value = mock_api_ctx
mock_api.__aexit__ = AsyncMock()
mock_api_class.return_value = mock_api
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
@ -49,11 +70,68 @@ async def test_formx(hass: HomeAssistant) -> None:
)
await hass.async_block_till_done()
mock_api_class.assert_called_once_with(ANY, "1.1.1.1")
mock_api.__aenter__.assert_called_once()
mock_api.__aexit__.assert_called_once()
mock_api_ctx.login.assert_called_once_with("test-password")
mock_api_ctx.get_setting_values.assert_called_once()
mock_apiclient_class.assert_called_once_with(ANY, "1.1.1.1")
mock_apiclient.__aenter__.assert_called_once()
mock_apiclient.__aexit__.assert_called_once()
mock_apiclient.login.assert_called_once_with("test-password")
mock_apiclient.get_settings.assert_called_once()
mock_apiclient.get_setting_values.assert_called_once_with(
"scb:network", "Hostname"
)
assert result2["type"] == "create_entry"
assert result2["title"] == "scb"
assert result2["data"] == {
"host": "1.1.1.1",
"password": "test-password",
}
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_g2(
hass: HomeAssistant,
mock_apiclient_class: type[ApiClient],
mock_apiclient: ApiClient,
) -> None:
"""Test the config flow for G2 models."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == "form"
assert result["errors"] == {}
with patch(
"homeassistant.components.kostal_plenticore.async_setup_entry",
return_value=True,
) as mock_setup_entry:
# mock of the context manager instance
mock_apiclient.login = AsyncMock()
mock_apiclient.get_settings = AsyncMock(
return_value={"scb:network": [SettingsData({"id": "Network:Hostname"})]}
)
mock_apiclient.get_setting_values = AsyncMock(
# G1 model has the entry id "Hostname"
return_value={"scb:network": {"Network:Hostname": "scb"}}
)
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"host": "1.1.1.1",
"password": "test-password",
},
)
await hass.async_block_till_done()
mock_apiclient_class.assert_called_once_with(ANY, "1.1.1.1")
mock_apiclient.__aenter__.assert_called_once()
mock_apiclient.__aexit__.assert_called_once()
mock_apiclient.login.assert_called_once_with("test-password")
mock_apiclient.get_settings.assert_called_once()
mock_apiclient.get_setting_values.assert_called_once_with(
"scb:network", "Network:Hostname"
)
assert result2["type"] == "create_entry"
assert result2["title"] == "scb"

View File

@ -0,0 +1,107 @@
"""Test Kostal Plenticore helper."""
from collections.abc import Generator
from unittest.mock import AsyncMock, MagicMock, patch
from pykoplenti import ApiClient, SettingsData
import pytest
from homeassistant.components.kostal_plenticore.const import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from tests.common import MockConfigEntry
@pytest.fixture
def mock_apiclient() -> Generator[ApiClient, None, None]:
"""Return a mocked ApiClient class."""
with patch(
"homeassistant.components.kostal_plenticore.helper.ApiClient",
autospec=True,
) as mock_api_class:
apiclient = MagicMock(spec=ApiClient)
apiclient.__aenter__.return_value = apiclient
apiclient.__aexit__ = AsyncMock()
mock_api_class.return_value = apiclient
yield apiclient
async def test_plenticore_async_setup_g1(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_apiclient: ApiClient,
) -> None:
"""Tests the async_setup() method of the Plenticore class for G1 models."""
mock_apiclient.get_settings = AsyncMock(
return_value={"scb:network": [SettingsData({"id": "Hostname"})]}
)
mock_apiclient.get_setting_values = AsyncMock(
# G1 model has the entry id "Hostname"
return_value={
"devices:local": {
"Properties:SerialNo": "12345",
"Branding:ProductName1": "PLENTICORE",
"Branding:ProductName2": "plus 10",
"Properties:VersionIOC": "01.45",
"Properties:VersionMC": "01.46",
},
"scb:network": {"Hostname": "scb"},
}
)
mock_config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
plenticore = hass.data[DOMAIN][mock_config_entry.entry_id]
assert plenticore.device_info == DeviceInfo(
configuration_url="http://192.168.1.2",
identifiers={(DOMAIN, "12345")},
manufacturer="Kostal",
model="PLENTICORE plus 10",
name="scb",
sw_version="IOC: 01.45 MC: 01.46",
)
async def test_plenticore_async_setup_g2(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_apiclient: ApiClient,
) -> None:
"""Tests the async_setup() method of the Plenticore class for G2 models."""
mock_apiclient.get_settings = AsyncMock(
return_value={"scb:network": [SettingsData({"id": "Network:Hostname"})]}
)
mock_apiclient.get_setting_values = AsyncMock(
# G1 model has the entry id "Hostname"
return_value={
"devices:local": {
"Properties:SerialNo": "12345",
"Branding:ProductName1": "PLENTICORE",
"Branding:ProductName2": "plus 10",
"Properties:VersionIOC": "01.45",
"Properties:VersionMC": "01.46",
},
"scb:network": {"Network:Hostname": "scb"},
}
)
mock_config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
plenticore = hass.data[DOMAIN][mock_config_entry.entry_id]
assert plenticore.device_info == DeviceInfo(
configuration_url="http://192.168.1.2",
identifiers={(DOMAIN, "12345")},
manufacturer="Kostal",
model="PLENTICORE plus 10",
name="scb",
sw_version="IOC: 01.45 MC: 01.46",
)

View File

@ -62,7 +62,20 @@ def mock_get_setting_values(mock_plenticore_client: ApiClient) -> list:
"id": "Battery:MinHomeComsumption",
}
),
]
],
"scb:network": [
SettingsData(
{
"min": "1",
"default": None,
"access": "readwrite",
"unit": None,
"id": "Hostname",
"type": "string",
"max": "63",
}
)
],
}
# this values are always retrieved by the integration on startup
@ -112,7 +125,22 @@ async def test_setup_no_entries(
) -> None:
"""Test that no entries are setup if Plenticore does not provide data."""
mock_plenticore_client.get_settings.return_value = []
# remove all settings except hostname which is used during setup
mock_plenticore_client.get_settings.return_value = {
"scb:network": [
SettingsData(
{
"min": "1",
"default": None,
"access": "readwrite",
"unit": None,
"id": "Hostname",
"type": "string",
"max": "63",
}
)
],
}
mock_config_entry.add_to_hass(hass)