Get MyStrom device state before checking support (#96004)

* Get device state before checking support

* Add full default device response to test

* Add test mocks

* Add test coverage
This commit is contained in:
Joost Lekkerkerker 2023-07-07 17:24:41 +02:00 committed by GitHub
parent f205d50ac7
commit ddd0d3faa2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 241 additions and 33 deletions

View File

@ -22,6 +22,24 @@ PLATFORMS_BULB = [Platform.LIGHT]
_LOGGER = logging.getLogger(__name__)
async def _async_get_device_state(
device: MyStromSwitch | MyStromBulb, ip_address: str
) -> None:
try:
await device.get_state()
except MyStromConnectionError as err:
_LOGGER.error("No route to myStrom plug: %s", ip_address)
raise ConfigEntryNotReady() from err
def _get_mystrom_bulb(host: str, mac: str) -> MyStromBulb:
return MyStromBulb(host, mac)
def _get_mystrom_switch(host: str) -> MyStromSwitch:
return MyStromSwitch(host)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up myStrom from a config entry."""
host = entry.data[CONF_HOST]
@ -34,12 +52,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
device_type = info["type"]
if device_type in [101, 106, 107]:
device = MyStromSwitch(host)
device = _get_mystrom_switch(host)
platforms = PLATFORMS_SWITCH
elif device_type == 102:
await _async_get_device_state(device, info["ip"])
elif device_type in [102, 105]:
mac = info["mac"]
device = MyStromBulb(host, mac)
device = _get_mystrom_bulb(host, mac)
platforms = PLATFORMS_BULB
await _async_get_device_state(device, info["ip"])
if device.bulb_type not in ["rgblamp", "strip"]:
_LOGGER.error(
"Device %s (%s) is not a myStrom bulb nor myStrom LED Strip",
@ -51,12 +71,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
_LOGGER.error("Unsupported myStrom device type: %s", device_type)
return False
try:
await device.get_state()
except MyStromConnectionError as err:
_LOGGER.error("No route to myStrom plug: %s", info["ip"])
raise ConfigEntryNotReady() from err
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = MyStromData(
device=device,
info=info,
@ -69,10 +83,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
device_type = hass.data[DOMAIN][entry.entry_id].info["type"]
platforms = []
if device_type in [101, 106, 107]:
platforms = PLATFORMS_SWITCH
elif device_type == 102:
platforms = PLATFORMS_BULB
platforms.extend(PLATFORMS_SWITCH)
elif device_type in [102, 105]:
platforms.extend(PLATFORMS_BULB)
if unload_ok := await hass.config_entries.async_unload_platforms(entry, platforms):
hass.data[DOMAIN].pop(entry.entry_id)

View File

@ -1 +1,172 @@
"""Tests for the myStrom integration."""
from typing import Any, Optional
def get_default_device_response(device_type: int) -> dict[str, Any]:
"""Return default device response."""
return {
"version": "2.59.32",
"mac": "6001940376EB",
"type": device_type,
"ssid": "personal",
"ip": "192.168.0.23",
"mask": "255.255.255.0",
"gw": "192.168.0.1",
"dns": "192.168.0.1",
"static": False,
"connected": True,
"signal": 94,
}
def get_default_bulb_state() -> dict[str, Any]:
"""Get default bulb state."""
return {
"type": "rgblamp",
"battery": False,
"reachable": True,
"meshroot": True,
"on": False,
"color": "46;18;100",
"mode": "hsv",
"ramp": 10,
"power": 0.45,
"fw_version": "2.58.0",
}
def get_default_switch_state() -> dict[str, Any]:
"""Get default switch state."""
return {
"power": 1.69,
"Ws": 0.81,
"relay": True,
"temperature": 24.87,
"version": "2.59.32",
"mac": "6001940376EB",
"ssid": "personal",
"ip": "192.168.0.23",
"mask": "255.255.255.0",
"gw": "192.168.0.1",
"dns": "192.168.0.1",
"static": False,
"connected": True,
"signal": 94,
}
class MyStromDeviceMock:
"""Base device mock."""
def __init__(self, state: dict[str, Any]) -> None:
"""Initialize device mock."""
self._requested_state = False
self._state = state
async def get_state(self) -> None:
"""Set if state is requested."""
self._requested_state = True
class MyStromBulbMock(MyStromDeviceMock):
"""MyStrom Bulb mock."""
def __init__(self, mac: str, state: dict[str, Any]) -> None:
"""Initialize bulb mock."""
super().__init__(state)
self.mac = mac
@property
def firmware(self) -> Optional[str]:
"""Return current firmware."""
if not self._requested_state:
return None
return self._state["fw_version"]
@property
def consumption(self) -> Optional[float]:
"""Return current firmware."""
if not self._requested_state:
return None
return self._state["power"]
@property
def color(self) -> Optional[str]:
"""Return current color settings."""
if not self._requested_state:
return None
return self._state["color"]
@property
def mode(self) -> Optional[str]:
"""Return current mode."""
if not self._requested_state:
return None
return self._state["mode"]
@property
def transition_time(self) -> Optional[int]:
"""Return current transition time (ramp)."""
if not self._requested_state:
return None
return self._state["ramp"]
@property
def bulb_type(self) -> Optional[str]:
"""Return the type of the bulb."""
if not self._requested_state:
return None
return self._state["type"]
@property
def state(self) -> Optional[bool]:
"""Return the current state of the bulb."""
if not self._requested_state:
return None
return self._state["on"]
class MyStromSwitchMock(MyStromDeviceMock):
"""MyStrom Switch mock."""
@property
def relay(self) -> Optional[bool]:
"""Return the relay state."""
if not self._requested_state:
return None
return self._state["on"]
@property
def consumption(self) -> Optional[float]:
"""Return the current power consumption in mWh."""
if not self._requested_state:
return None
return self._state["power"]
@property
def consumedWs(self) -> Optional[float]:
"""The average of energy consumed per second since last report call."""
if not self._requested_state:
return None
return self._state["Ws"]
@property
def firmware(self) -> Optional[str]:
"""Return the current firmware."""
if not self._requested_state:
return None
return self._state["version"]
@property
def mac(self) -> Optional[str]:
"""Return the MAC address."""
if not self._requested_state:
return None
return self._state["mac"]
@property
def temperature(self) -> Optional[float]:
"""Return the current temperature in celsius."""
if not self._requested_state:
return None
return self._state["temperature"]

View File

@ -2,11 +2,19 @@
from unittest.mock import AsyncMock, PropertyMock, patch
from pymystrom.exceptions import MyStromConnectionError
import pytest
from homeassistant.components.mystrom.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from . import (
MyStromBulbMock,
MyStromSwitchMock,
get_default_bulb_state,
get_default_device_response,
get_default_switch_state,
)
from .conftest import DEVICE_MAC
from tests.common import MockConfigEntry
@ -16,30 +24,21 @@ async def init_integration(
hass: HomeAssistant,
config_entry: MockConfigEntry,
device_type: int,
bulb_type: str = "strip",
) -> None:
"""Inititialize integration for testing."""
with patch(
"pymystrom.get_device_info",
side_effect=AsyncMock(return_value={"type": device_type, "mac": DEVICE_MAC}),
), patch("pymystrom.switch.MyStromSwitch.get_state", return_value={}), patch(
"pymystrom.bulb.MyStromBulb.get_state", return_value={}
side_effect=AsyncMock(return_value=get_default_device_response(device_type)),
), patch(
"pymystrom.bulb.MyStromBulb.bulb_type", bulb_type
"homeassistant.components.mystrom._get_mystrom_bulb",
return_value=MyStromBulbMock("6001940376EB", get_default_bulb_state()),
), patch(
"pymystrom.switch.MyStromSwitch.mac",
new_callable=PropertyMock,
return_value=DEVICE_MAC,
), patch(
"pymystrom.bulb.MyStromBulb.mac",
new_callable=PropertyMock,
return_value=DEVICE_MAC,
"homeassistant.components.mystrom._get_mystrom_switch",
return_value=MyStromSwitchMock(get_default_switch_state()),
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state == ConfigEntryState.LOADED
async def test_init_switch_and_unload(
hass: HomeAssistant, config_entry: MockConfigEntry
@ -56,12 +55,35 @@ async def test_init_switch_and_unload(
assert not hass.data.get(DOMAIN)
async def test_init_bulb(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
@pytest.mark.parametrize(
("device_type", "platform", "entry_state", "entity_state_none"),
[
(101, "switch", ConfigEntryState.LOADED, False),
(102, "light", ConfigEntryState.LOADED, False),
(103, "button", ConfigEntryState.SETUP_ERROR, True),
(104, "button", ConfigEntryState.SETUP_ERROR, True),
(105, "light", ConfigEntryState.LOADED, False),
(106, "switch", ConfigEntryState.LOADED, False),
(107, "switch", ConfigEntryState.LOADED, False),
(110, "sensor", ConfigEntryState.SETUP_ERROR, True),
(113, "switch", ConfigEntryState.SETUP_ERROR, True),
(118, "button", ConfigEntryState.SETUP_ERROR, True),
(120, "switch", ConfigEntryState.SETUP_ERROR, True),
],
)
async def test_init_bulb(
hass: HomeAssistant,
config_entry: MockConfigEntry,
device_type: int,
platform: str,
entry_state: ConfigEntryState,
entity_state_none: bool,
) -> None:
"""Test the initialization of a myStrom bulb."""
await init_integration(hass, config_entry, 102)
state = hass.states.get("light.mystrom_device")
assert state is not None
assert config_entry.state is ConfigEntryState.LOADED
await init_integration(hass, config_entry, device_type)
state = hass.states.get(f"{platform}.mystrom_device")
assert (state is None) == entity_state_none
assert config_entry.state is entry_state
async def test_init_of_unknown_bulb(
@ -120,7 +142,7 @@ async def test_init_cannot_connect_because_of_get_state(
"""Test error handling for failing get_state."""
with patch(
"pymystrom.get_device_info",
side_effect=AsyncMock(return_value={"type": 101, "mac": DEVICE_MAC}),
side_effect=AsyncMock(return_value=get_default_device_response(101)),
), patch(
"pymystrom.switch.MyStromSwitch.get_state", side_effect=MyStromConnectionError()
), patch(
@ -129,4 +151,4 @@ async def test_init_cannot_connect_because_of_get_state(
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state == ConfigEntryState.SETUP_ERROR
assert config_entry.state == ConfigEntryState.SETUP_RETRY