Post System Bridge 4.x.x integration improvements (#112189)

* Dont remove api key during migration

* Fix return

* Fix test

* Make lambda more readable

* Move fixtures to init, move migration test to test_init.py

* Refactor config_entry data assignment

* Refactor system_bridge migration tests

* Fix type for debug message

* Fix type for debug message

* Remove duplicated unused code (rebase error)

* Refactor test_migration_minor_2_to_1 to test_migration_minor_future_to_2

* Fix version check in async_migrate_entry

* Update migration logic to handle future minor version

* Add ConfigEntryState assertion in test_init.py

* Change condition to minor_version < 2

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Refactor system bridge migration tests

* Remove minor downgrade code

* Update tests/components/system_bridge/test_init.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update tests/components/system_bridge/test_init.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Apply suggestions from code review

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Move dataclass to store requested data to data.py

* Use dataclass in config flow

* Move media player and sensor onto data.py dataclass

* Move data and handler inside validate

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Aidan Timson 2024-03-06 09:47:21 +00:00 committed by GitHub
parent 39cad5f1ee
commit 2599252600
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 302 additions and 292 deletions

View File

@ -45,6 +45,7 @@ from homeassistant.helpers import (
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from .config_flow import SystemBridgeConfigFlow
from .const import DOMAIN, MODULES
from .coordinator import SystemBridgeDataUpdateCoordinator
@ -358,13 +359,19 @@ async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Migrate old entry."""
_LOGGER.debug("Migrating from version %s", config_entry.version)
_LOGGER.debug(
"Migrating from version %s.%s",
config_entry.version,
config_entry.minor_version,
)
if config_entry.version == 1 and config_entry.minor_version == 1:
if config_entry.version > SystemBridgeConfigFlow.VERSION:
return False
if config_entry.minor_version < 2:
# Migrate to CONF_TOKEN, which was added in 1.2
new_data = dict(config_entry.data)
new_data.setdefault(CONF_TOKEN, config_entry.data.get(CONF_API_KEY))
new_data.pop(CONF_API_KEY, None)
hass.config_entries.async_update_entry(
config_entry,
@ -378,5 +385,4 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
config_entry.minor_version,
)
# User is trying to downgrade from a future version
return False
return True

View File

@ -12,18 +12,19 @@ from systembridgeconnector.exceptions import (
ConnectionErrorException,
)
from systembridgeconnector.websocket_client import WebSocketClient
from systembridgemodels.modules import GetData, System
from systembridgemodels.modules import GetData
import voluptuous as vol
from homeassistant.components import zeroconf
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT, CONF_TOKEN
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TOKEN
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
from .data import SystemBridgeData
_LOGGER = logging.getLogger(__name__)
@ -45,25 +46,40 @@ async def _validate_input(
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
"""
host = data[CONF_HOST]
system_bridge_data = SystemBridgeData()
async def _async_handle_module(
module_name: str,
module: Any,
) -> None:
"""Handle data from the WebSocket client."""
_LOGGER.debug("Set new data for: %s", module_name)
setattr(system_bridge_data, module_name, module)
websocket_client = WebSocketClient(
host,
data[CONF_HOST],
data[CONF_PORT],
data[CONF_API_KEY],
data[CONF_TOKEN],
)
try:
async with asyncio.timeout(15):
await websocket_client.connect(session=async_get_clientsession(hass))
hass.async_create_task(websocket_client.listen())
hass.async_create_task(
websocket_client.listen(callback=_async_handle_module)
)
response = await websocket_client.get_data(GetData(modules=["system"]))
_LOGGER.debug("Got response: %s", response.json())
if response.data is None or not isinstance(response.data, System):
_LOGGER.debug("Got response: %s", response)
if response is None:
raise CannotConnect("No data received")
system: System = response.data
while system_bridge_data.system is None:
await asyncio.sleep(0.2)
except AuthenticationException as exception:
_LOGGER.warning(
"Authentication error when connecting to %s: %s", data[CONF_HOST], exception
"Authentication error when connecting to %s: %s",
data[CONF_HOST],
exception,
)
raise InvalidAuth from exception
except (
@ -80,9 +96,9 @@ async def _validate_input(
except ValueError as exception:
raise CannotConnect from exception
_LOGGER.debug("Got System data: %s", system.json())
_LOGGER.debug("Got System data: %s", system_bridge_data.system)
return {"hostname": host, "uuid": system.uuid}
return {"hostname": data[CONF_HOST], "uuid": system_bridge_data.system.uuid}
async def _async_get_info(
@ -120,93 +136,6 @@ class SystemBridgeConfigFlow(
self._name: str | None = None
self._input: dict[str, Any] = {}
self._reauth = False
self._system_data: System | None = None
async def _validate_input(
self,
data: dict[str, Any],
) -> dict[str, str]:
"""Validate the user input allows us to connect.
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
"""
host = data[CONF_HOST]
websocket_client = WebSocketClient(
host,
data[CONF_PORT],
data[CONF_TOKEN],
)
async def async_handle_module(
module_name: str,
module: Any,
) -> None:
"""Handle data from the WebSocket client."""
_LOGGER.debug("Set new data for: %s", module_name)
if module_name == "system":
self._system_data = module
try:
async with asyncio.timeout(15):
await websocket_client.connect(
session=async_get_clientsession(self.hass)
)
self.hass.async_create_task(
websocket_client.listen(callback=async_handle_module)
)
response = await websocket_client.get_data(GetData(modules=["system"]))
_LOGGER.debug("Got response: %s", response)
if response is None:
raise CannotConnect("No data received")
while self._system_data is None:
await asyncio.sleep(0.2)
except AuthenticationException as exception:
_LOGGER.warning(
"Authentication error when connecting to %s: %s",
data[CONF_HOST],
exception,
)
raise InvalidAuth from exception
except (
ConnectionClosedException,
ConnectionErrorException,
) as exception:
_LOGGER.warning(
"Connection error when connecting to %s: %s", data[CONF_HOST], exception
)
raise CannotConnect from exception
except TimeoutError as exception:
_LOGGER.warning(
"Timed out connecting to %s: %s", data[CONF_HOST], exception
)
raise CannotConnect from exception
except ValueError as exception:
raise CannotConnect from exception
_LOGGER.debug("Got System data: %s", self._system_data)
return {"hostname": host, "uuid": self._system_data.uuid}
async def _async_get_info(
self,
user_input: dict[str, Any],
) -> tuple[dict[str, str], dict[str, str] | None]:
errors = {}
try:
info = await self._validate_input(user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
return errors, info
return errors, None
async def async_step_user(
self, user_input: dict[str, Any] | None = None
@ -217,7 +146,7 @@ class SystemBridgeConfigFlow(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA
)
errors, info = await self._async_get_info(user_input)
errors, info = await _async_get_info(self.hass, user_input)
if not errors and info is not None:
# Check if already configured
await self.async_set_unique_id(info["uuid"], raise_on_progress=False)
@ -237,7 +166,7 @@ class SystemBridgeConfigFlow(
if user_input is not None:
user_input = {**self._input, **user_input}
errors, info = await self._async_get_info(user_input)
errors, info = await _async_get_info(self.hass, user_input)
if not errors and info is not None:
# Check if already configured
existing_entry = await self.async_set_unique_id(info["uuid"])

View File

@ -3,7 +3,6 @@ from __future__ import annotations
import asyncio
from collections.abc import Callable
from dataclasses import dataclass, field
from datetime import timedelta
import logging
from typing import Any
@ -18,19 +17,7 @@ from systembridgemodels.media_directories import MediaDirectory
from systembridgemodels.media_files import MediaFile, MediaFiles
from systembridgemodels.media_get_file import MediaGetFile
from systembridgemodels.media_get_files import MediaGetFiles
from systembridgemodels.modules import (
CPU,
GPU,
Battery,
Disks,
Display,
GetData,
Media,
Memory,
Process,
RegisterDataListener,
System,
)
from systembridgemodels.modules import GetData, RegisterDataListener
from systembridgemodels.response import Response
from homeassistant.config_entries import ConfigEntry
@ -46,26 +33,10 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN, MODULES
from .data import SystemBridgeData
@dataclass
class SystemBridgeCoordinatorData:
"""System Bridge Coordianator Data."""
battery: Battery = field(default_factory=Battery)
cpu: CPU = field(default_factory=CPU)
disks: Disks = None
displays: list[Display] = field(default_factory=list[Display])
gpus: list[GPU] = field(default_factory=list[GPU])
media: Media = field(default_factory=Media)
memory: Memory = None
processes: list[Process] = field(default_factory=list[Process])
system: System = None
class SystemBridgeDataUpdateCoordinator(
DataUpdateCoordinator[SystemBridgeCoordinatorData]
):
class SystemBridgeDataUpdateCoordinator(DataUpdateCoordinator[SystemBridgeData]):
"""Class to manage fetching System Bridge data from single endpoint."""
def __init__(
@ -79,7 +50,7 @@ class SystemBridgeDataUpdateCoordinator(
self.title = entry.title
self.unsub: Callable | None = None
self.systembridge_data = SystemBridgeCoordinatorData()
self.systembridge_data = SystemBridgeData()
self.websocket_client = WebSocketClient(
entry.data[CONF_HOST],
entry.data[CONF_PORT],
@ -247,7 +218,7 @@ class SystemBridgeDataUpdateCoordinator(
EVENT_HOMEASSISTANT_STOP, close_websocket
)
async def _async_update_data(self) -> SystemBridgeCoordinatorData:
async def _async_update_data(self) -> SystemBridgeData:
"""Update System Bridge data from WebSocket."""
self.logger.debug(
"_async_update_data - WebSocket Connected: %s",

View File

@ -0,0 +1,29 @@
"""System Bridge integration data."""
from dataclasses import dataclass, field
from systembridgemodels.modules import (
CPU,
GPU,
Battery,
Disks,
Display,
Media,
Memory,
Process,
System,
)
@dataclass
class SystemBridgeData:
"""System Bridge Data."""
battery: Battery = field(default_factory=Battery)
cpu: CPU = field(default_factory=CPU)
disks: Disks = None
displays: list[Display] = field(default_factory=list[Display])
gpus: list[GPU] = field(default_factory=list[GPU])
media: Media = field(default_factory=Media)
memory: Memory = None
processes: list[Process] = field(default_factory=list[Process])
system: System = None

View File

@ -20,7 +20,8 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .coordinator import SystemBridgeCoordinatorData, SystemBridgeDataUpdateCoordinator
from .coordinator import SystemBridgeDataUpdateCoordinator
from .data import SystemBridgeData
from .entity import SystemBridgeEntity
STATUS_CHANGING: Final[str] = "CHANGING"
@ -126,7 +127,7 @@ class SystemBridgeMediaPlayer(SystemBridgeEntity, MediaPlayerEntity):
return features
@property
def _systembridge_data(self) -> SystemBridgeCoordinatorData:
def _systembridge_data(self) -> SystemBridgeData:
"""Return data for the entity."""
return self.coordinator.data

View File

@ -33,7 +33,8 @@ from homeassistant.helpers.typing import UNDEFINED, StateType
from homeassistant.util import dt as dt_util
from .const import DOMAIN
from .coordinator import SystemBridgeCoordinatorData, SystemBridgeDataUpdateCoordinator
from .coordinator import SystemBridgeDataUpdateCoordinator
from .data import SystemBridgeData
from .entity import SystemBridgeEntity
ATTR_AVAILABLE: Final = "available"
@ -53,14 +54,14 @@ class SystemBridgeSensorEntityDescription(SensorEntityDescription):
value: Callable = round
def battery_time_remaining(data: SystemBridgeCoordinatorData) -> datetime | None:
def battery_time_remaining(data: SystemBridgeData) -> datetime | None:
"""Return the battery time remaining."""
if (battery_time := data.battery.time_remaining) is not None:
return dt_util.utcnow() + timedelta(seconds=battery_time)
return None
def cpu_speed(data: SystemBridgeCoordinatorData) -> float | None:
def cpu_speed(data: SystemBridgeData) -> float | None:
"""Return the CPU speed."""
if (cpu_frequency := data.cpu.frequency) is not None and (
cpu_frequency.current
@ -72,7 +73,7 @@ def cpu_speed(data: SystemBridgeCoordinatorData) -> float | None:
def with_per_cpu(func) -> Callable:
"""Wrap a function to ensure per CPU data is available."""
def wrapper(data: SystemBridgeCoordinatorData, index: int) -> float | None:
def wrapper(data: SystemBridgeData, index: int) -> float | None:
"""Wrap a function to ensure per CPU data is available."""
if data.cpu.per_cpu is not None and index < len(data.cpu.per_cpu):
return func(data.cpu.per_cpu[index])
@ -96,7 +97,7 @@ def cpu_usage_per_cpu(per_cpu: PerCPU) -> float | None:
def with_display(func) -> Callable:
"""Wrap a function to ensure a Display is available."""
def wrapper(data: SystemBridgeCoordinatorData, index: int) -> Display | None:
def wrapper(data: SystemBridgeData, index: int) -> Display | None:
"""Wrap a function to ensure a Display is available."""
if index < len(data.displays):
return func(data.displays[index])
@ -126,7 +127,7 @@ def display_refresh_rate(display: Display) -> float | None:
def with_gpu(func) -> Callable:
"""Wrap a function to ensure a GPU is available."""
def wrapper(data: SystemBridgeCoordinatorData, index: int) -> GPU | None:
def wrapper(data: SystemBridgeData, index: int) -> GPU | None:
"""Wrap a function to ensure a GPU is available."""
if index < len(data.gpus):
return func(data.gpus[index])
@ -191,7 +192,7 @@ def gpu_usage_percentage(gpu: GPU) -> float | None:
return gpu.core_load
def memory_free(data: SystemBridgeCoordinatorData) -> float | None:
def memory_free(data: SystemBridgeData) -> float | None:
"""Return the free memory."""
if (virtual := data.memory.virtual) is not None and (
free := virtual.free
@ -200,7 +201,7 @@ def memory_free(data: SystemBridgeCoordinatorData) -> float | None:
return None
def memory_used(data: SystemBridgeCoordinatorData) -> float | None:
def memory_used(data: SystemBridgeData) -> float | None:
"""Return the used memory."""
if (virtual := data.memory.virtual) is not None and (
used := virtual.used
@ -210,7 +211,7 @@ def memory_used(data: SystemBridgeCoordinatorData) -> float | None:
def partition_usage(
data: SystemBridgeCoordinatorData,
data: SystemBridgeData,
device_index: int,
partition_index: int,
) -> float | None:
@ -382,9 +383,11 @@ async def async_setup_entry(
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
icon="mdi:harddisk",
value=lambda data,
dk=index_device,
pk=index_partition: partition_usage(data, dk, pk),
value=(
lambda data,
dk=index_device,
pk=index_partition: partition_usage(data, dk, pk)
),
),
entry.data[CONF_PORT],
)

View File

@ -1 +1,116 @@
"""Tests for the System Bridge integration."""
from collections.abc import Awaitable, Callable
from dataclasses import asdict
from ipaddress import ip_address
from typing import Any
from systembridgeconnector.const import TYPE_DATA_UPDATE
from systembridgemodels.const import MODEL_SYSTEM
from systembridgemodels.modules import System
from systembridgemodels.response import Response
from homeassistant.components import zeroconf
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TOKEN
FIXTURE_MAC_ADDRESS = "aa:bb:cc:dd:ee:ff"
FIXTURE_UUID = "e91bf575-56f3-4c83-8f42-70ac17adcd33"
FIXTURE_AUTH_INPUT = {CONF_TOKEN: "abc-123-def-456-ghi"}
FIXTURE_USER_INPUT = {
CONF_TOKEN: "abc-123-def-456-ghi",
CONF_HOST: "test-bridge",
CONF_PORT: "9170",
}
FIXTURE_ZEROCONF_INPUT = {
CONF_TOKEN: "abc-123-def-456-ghi",
CONF_HOST: "1.1.1.1",
CONF_PORT: "9170",
}
FIXTURE_ZEROCONF = zeroconf.ZeroconfServiceInfo(
ip_address=ip_address("1.1.1.1"),
ip_addresses=[ip_address("1.1.1.1")],
port=9170,
hostname="test-bridge.local.",
type="_system-bridge._tcp.local.",
name="System Bridge - test-bridge._system-bridge._tcp.local.",
properties={
"address": "http://test-bridge:9170",
"fqdn": "test-bridge",
"host": "test-bridge",
"ip": "1.1.1.1",
"mac": FIXTURE_MAC_ADDRESS,
"port": "9170",
"uuid": FIXTURE_UUID,
},
)
FIXTURE_ZEROCONF_BAD = zeroconf.ZeroconfServiceInfo(
ip_address=ip_address("1.1.1.1"),
ip_addresses=[ip_address("1.1.1.1")],
port=9170,
hostname="test-bridge.local.",
type="_system-bridge._tcp.local.",
name="System Bridge - test-bridge._system-bridge._tcp.local.",
properties={
"something": "bad",
},
)
FIXTURE_SYSTEM = System(
boot_time=1,
fqdn="",
hostname="1.1.1.1",
ip_address_4="1.1.1.1",
mac_address=FIXTURE_MAC_ADDRESS,
platform="",
platform_version="",
uptime=1,
uuid=FIXTURE_UUID,
version="",
version_latest="",
version_newer_available=False,
users=[],
)
FIXTURE_DATA_RESPONSE = Response(
id="1234",
type=TYPE_DATA_UPDATE,
subtype=None,
message="Data received",
module=MODEL_SYSTEM,
data=asdict(FIXTURE_SYSTEM),
)
FIXTURE_DATA_RESPONSE_BAD = Response(
id="1234",
type=TYPE_DATA_UPDATE,
subtype=None,
message="Data received",
module=MODEL_SYSTEM,
data={},
)
FIXTURE_DATA_RESPONSE_BAD = Response(
id="1234",
type=TYPE_DATA_UPDATE,
subtype=None,
message="Data received",
module=MODEL_SYSTEM,
data={},
)
async def mock_data_listener(
self,
callback: Callable[[str, Any], Awaitable[None]] | None = None,
_: bool = False,
):
"""Mock websocket data listener."""
if callback is not None:
# Simulate data received from the websocket
await callback(MODEL_SYSTEM, FIXTURE_SYSTEM)

View File

@ -1,131 +1,29 @@
"""Test the System Bridge config flow."""
from collections.abc import Awaitable, Callable
from dataclasses import asdict
from ipaddress import ip_address
from typing import Any
from unittest.mock import patch
from systembridgeconnector.const import TYPE_DATA_UPDATE
from systembridgeconnector.exceptions import (
AuthenticationException,
ConnectionClosedException,
ConnectionErrorException,
)
from systembridgemodels.const import MODEL_SYSTEM
from systembridgemodels.modules.system import System
from systembridgemodels.response import Response
from homeassistant import config_entries, data_entry_flow
from homeassistant.components import zeroconf
from homeassistant.components.system_bridge.config_flow import SystemBridgeConfigFlow
from homeassistant.components.system_bridge.const import DOMAIN
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT, CONF_TOKEN
from homeassistant.core import HomeAssistant
from . import (
FIXTURE_AUTH_INPUT,
FIXTURE_DATA_RESPONSE,
FIXTURE_USER_INPUT,
FIXTURE_UUID,
FIXTURE_ZEROCONF,
FIXTURE_ZEROCONF_BAD,
FIXTURE_ZEROCONF_INPUT,
mock_data_listener,
)
from tests.common import MockConfigEntry
FIXTURE_MAC_ADDRESS = "aa:bb:cc:dd:ee:ff"
FIXTURE_UUID = "e91bf575-56f3-4c83-8f42-70ac17adcd33"
FIXTURE_AUTH_INPUT = {CONF_TOKEN: "abc-123-def-456-ghi"}
FIXTURE_USER_INPUT = {
CONF_TOKEN: "abc-123-def-456-ghi",
CONF_HOST: "test-bridge",
CONF_PORT: "9170",
}
FIXTURE_ZEROCONF_INPUT = {
CONF_TOKEN: "abc-123-def-456-ghi",
CONF_HOST: "1.1.1.1",
CONF_PORT: "9170",
}
FIXTURE_ZEROCONF = zeroconf.ZeroconfServiceInfo(
ip_address=ip_address("1.1.1.1"),
ip_addresses=[ip_address("1.1.1.1")],
port=9170,
hostname="test-bridge.local.",
type="_system-bridge._tcp.local.",
name="System Bridge - test-bridge._system-bridge._tcp.local.",
properties={
"address": "http://test-bridge:9170",
"fqdn": "test-bridge",
"host": "test-bridge",
"ip": "1.1.1.1",
"mac": FIXTURE_MAC_ADDRESS,
"port": "9170",
"uuid": FIXTURE_UUID,
},
)
FIXTURE_ZEROCONF_BAD = zeroconf.ZeroconfServiceInfo(
ip_address=ip_address("1.1.1.1"),
ip_addresses=[ip_address("1.1.1.1")],
port=9170,
hostname="test-bridge.local.",
type="_system-bridge._tcp.local.",
name="System Bridge - test-bridge._system-bridge._tcp.local.",
properties={
"something": "bad",
},
)
FIXTURE_SYSTEM = System(
boot_time=1,
fqdn="",
hostname="1.1.1.1",
ip_address_4="1.1.1.1",
mac_address=FIXTURE_MAC_ADDRESS,
platform="",
platform_version="",
uptime=1,
uuid=FIXTURE_UUID,
version="",
version_latest="",
version_newer_available=False,
users=[],
)
FIXTURE_DATA_RESPONSE = Response(
id="1234",
type=TYPE_DATA_UPDATE,
subtype=None,
message="Data received",
module=MODEL_SYSTEM,
data=asdict(FIXTURE_SYSTEM),
)
FIXTURE_DATA_RESPONSE_BAD = Response(
id="1234",
type=TYPE_DATA_UPDATE,
subtype=None,
message="Data received",
module=MODEL_SYSTEM,
data={},
)
FIXTURE_DATA_RESPONSE_BAD = Response(
id="1234",
type=TYPE_DATA_UPDATE,
subtype=None,
message="Data received",
module=MODEL_SYSTEM,
data={},
)
async def mock_data_listener(
self,
callback: Callable[[str, Any], Awaitable[None]] | None = None,
_: bool = False,
):
"""Mock websocket data listener."""
if callback is not None:
# Simulate data received from the websocket
await callback(MODEL_SYSTEM, FIXTURE_SYSTEM)
async def test_show_user_form(hass: HomeAssistant) -> None:
"""Test that the setup form is served."""
@ -536,28 +434,3 @@ async def test_zeroconf_bad_zeroconf_info(hass: HomeAssistant) -> None:
assert result["type"] == data_entry_flow.FlowResultType.ABORT
assert result["reason"] == "unknown"
async def test_migration(hass: HomeAssistant) -> None:
"""Test migration from system_bridge to system_bridge."""
config_entry = MockConfigEntry(
domain=DOMAIN,
unique_id=FIXTURE_UUID,
data={
CONF_API_KEY: FIXTURE_USER_INPUT[CONF_TOKEN],
CONF_HOST: FIXTURE_USER_INPUT[CONF_HOST],
CONF_PORT: FIXTURE_USER_INPUT[CONF_PORT],
},
version=1,
minor_version=1,
)
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Check that the version has been updated and the api_key has been moved to token
assert config_entry.version == SystemBridgeConfigFlow.VERSION
assert config_entry.minor_version == SystemBridgeConfigFlow.MINOR_VERSION
assert CONF_API_KEY not in config_entry.data
assert config_entry.data[CONF_TOKEN] == FIXTURE_USER_INPUT[CONF_TOKEN]
assert config_entry.data == FIXTURE_USER_INPUT

View File

@ -0,0 +1,83 @@
"""Test the System Bridge integration."""
from unittest.mock import patch
from homeassistant.components.system_bridge.config_flow import SystemBridgeConfigFlow
from homeassistant.components.system_bridge.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT, CONF_TOKEN
from homeassistant.core import HomeAssistant
from . import FIXTURE_USER_INPUT, FIXTURE_UUID
from tests.common import MockConfigEntry
async def test_migration_minor_1_to_2(hass: HomeAssistant) -> None:
"""Test migration."""
config_entry = MockConfigEntry(
domain=DOMAIN,
unique_id=FIXTURE_UUID,
data={
CONF_API_KEY: FIXTURE_USER_INPUT[CONF_TOKEN],
CONF_HOST: FIXTURE_USER_INPUT[CONF_HOST],
CONF_PORT: FIXTURE_USER_INPUT[CONF_PORT],
},
version=SystemBridgeConfigFlow.VERSION,
minor_version=1,
)
with patch(
"homeassistant.components.system_bridge.async_setup_entry",
return_value=True,
) as mock_setup_entry:
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 1
# Check that the version has been updated and the api_key has been moved to token
assert config_entry.version == SystemBridgeConfigFlow.VERSION
assert config_entry.minor_version == SystemBridgeConfigFlow.MINOR_VERSION
assert config_entry.data == {
CONF_API_KEY: FIXTURE_USER_INPUT[CONF_TOKEN],
CONF_HOST: FIXTURE_USER_INPUT[CONF_HOST],
CONF_PORT: FIXTURE_USER_INPUT[CONF_PORT],
CONF_TOKEN: FIXTURE_USER_INPUT[CONF_TOKEN],
}
assert config_entry.state == ConfigEntryState.LOADED
async def test_migration_minor_future_version(hass: HomeAssistant) -> None:
"""Test migration."""
config_entry_data = {
CONF_API_KEY: FIXTURE_USER_INPUT[CONF_TOKEN],
CONF_HOST: FIXTURE_USER_INPUT[CONF_HOST],
CONF_PORT: FIXTURE_USER_INPUT[CONF_PORT],
CONF_TOKEN: FIXTURE_USER_INPUT[CONF_TOKEN],
}
config_entry_version = SystemBridgeConfigFlow.VERSION
config_entry_minor_version = SystemBridgeConfigFlow.MINOR_VERSION + 1
config_entry = MockConfigEntry(
domain=DOMAIN,
unique_id=FIXTURE_UUID,
data=config_entry_data,
version=config_entry_version,
minor_version=config_entry_minor_version,
)
with patch(
"homeassistant.components.system_bridge.async_setup_entry",
return_value=True,
) as mock_setup_entry:
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 1
assert config_entry.version == config_entry_version
assert config_entry.minor_version == config_entry_minor_version
assert config_entry.data == config_entry_data
assert config_entry.state == ConfigEntryState.LOADED