hass-core/tests/components/tplink/test_init.py
Michael bedb9550f5
Move TP-Link power and energy switch attributes to sensors (#53596)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2021-07-29 11:02:47 -07:00

460 lines
16 KiB
Python

"""Tests for the TP-Link component."""
from __future__ import annotations
from datetime import datetime
import time
from typing import Any
from unittest.mock import MagicMock, patch
from pyHS100 import SmartBulb, SmartDevice, SmartDeviceException, SmartPlug, smartstrip
from pyHS100.smartdevice import EmeterStatus
import pytest
from homeassistant import config_entries, data_entry_flow
from homeassistant.components import tplink
from homeassistant.components.sensor import ATTR_LAST_RESET
from homeassistant.components.switch import ATTR_CURRENT_POWER_W, ATTR_TODAY_ENERGY_KWH
from homeassistant.components.tplink.common import SmartDevices
from homeassistant.components.tplink.const import (
ATTR_CURRENT_A,
ATTR_TOTAL_ENERGY_KWH,
CONF_DIMMER,
CONF_DISCOVERY,
CONF_EMETER_PARAMS,
CONF_LIGHT,
CONF_MODEL,
CONF_SW_VERSION,
CONF_SWITCH,
COORDINATORS,
)
from homeassistant.const import (
ATTR_VOLTAGE,
CONF_ALIAS,
CONF_DEVICE_ID,
CONF_HOST,
CONF_MAC,
)
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from homeassistant.util.dt import utc_from_timestamp
from tests.common import MockConfigEntry, mock_coro
from tests.components.tplink.consts import SMARTPLUGSWITCH_DATA, SMARTSTRIPWITCH_DATA
async def test_creating_entry_tries_discover(hass):
"""Test setting up does discovery."""
with patch(
"homeassistant.components.tplink.async_setup_entry",
return_value=mock_coro(True),
) as mock_setup, patch(
"homeassistant.components.tplink.common.Discover.discover",
return_value={"host": 1234},
):
result = await hass.config_entries.flow.async_init(
tplink.DOMAIN, context={"source": config_entries.SOURCE_USER}
)
# Confirmation form
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
await hass.async_block_till_done()
assert len(mock_setup.mock_calls) == 1
async def test_configuring_tplink_causes_discovery(hass):
"""Test that specifying empty config does discovery."""
with patch("homeassistant.components.tplink.common.Discover.discover") as discover:
discover.return_value = {"host": 1234}
await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
await hass.async_block_till_done()
assert len(discover.mock_calls) == 1
@pytest.mark.parametrize(
"name,cls,platform",
[
("pyHS100.SmartPlug", SmartPlug, "switch"),
("pyHS100.SmartBulb", SmartBulb, "light"),
],
)
@pytest.mark.parametrize("count", [1, 2, 3])
async def test_configuring_device_types(hass, name, cls, platform, count):
"""Test that light or switch platform list is filled correctly."""
with patch(
"homeassistant.components.tplink.common.Discover.discover"
) as discover, patch(
"homeassistant.components.tplink.common.SmartDevice._query_helper"
), patch(
"homeassistant.components.tplink.light.async_setup_entry",
return_value=True,
):
discovery_data = {
f"123.123.123.{c}": cls("123.123.123.123") for c in range(count)
}
discover.return_value = discovery_data
await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
await hass.async_block_till_done()
assert len(discover.mock_calls) == 1
assert len(hass.data[tplink.DOMAIN][platform]) == count
class UnknownSmartDevice(SmartDevice):
"""Dummy class for testing."""
@property
def has_emeter(self) -> bool:
"""Do nothing."""
def turn_off(self) -> None:
"""Do nothing."""
def turn_on(self) -> None:
"""Do nothing."""
@property
def is_on(self) -> bool:
"""Do nothing."""
@property
def state_information(self) -> dict[str, Any]:
"""Do nothing."""
async def test_configuring_devices_from_multiple_sources(hass):
"""Test static and discover devices are not duplicated."""
with patch(
"homeassistant.components.tplink.common.Discover.discover"
) as discover, patch(
"homeassistant.components.tplink.common.SmartDevice._query_helper"
), patch(
"homeassistant.config_entries.ConfigEntries.async_forward_entry_setup"
):
discover_device_fail = SmartPlug("123.123.123.123")
discover_device_fail.get_sysinfo = MagicMock(side_effect=SmartDeviceException())
discover.return_value = {
"123.123.123.1": SmartBulb("123.123.123.1"),
"123.123.123.2": SmartPlug("123.123.123.2"),
"123.123.123.3": SmartBulb("123.123.123.3"),
"123.123.123.4": SmartPlug("123.123.123.4"),
"123.123.123.123": discover_device_fail,
"123.123.123.124": UnknownSmartDevice("123.123.123.124"),
}
await async_setup_component(
hass,
tplink.DOMAIN,
{
tplink.DOMAIN: {
CONF_LIGHT: [{CONF_HOST: "123.123.123.1"}],
CONF_SWITCH: [{CONF_HOST: "123.123.123.2"}],
CONF_DIMMER: [{CONF_HOST: "123.123.123.22"}],
}
},
)
await hass.async_block_till_done()
assert len(discover.mock_calls) == 1
assert len(hass.data[tplink.DOMAIN][CONF_LIGHT]) == 3
assert len(hass.data[tplink.DOMAIN][CONF_SWITCH]) == 2
async def test_is_dimmable(hass):
"""Test that is_dimmable switches are correctly added as lights."""
with patch(
"homeassistant.components.tplink.common.Discover.discover"
) as discover, patch(
"homeassistant.components.tplink.light.async_setup_entry",
return_value=mock_coro(True),
) as setup, patch(
"homeassistant.components.tplink.common.SmartDevice._query_helper"
), patch(
"homeassistant.components.tplink.common.SmartPlug.is_dimmable", True
):
dimmable_switch = SmartPlug("123.123.123.123")
discover.return_value = {"host": dimmable_switch}
await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
await hass.async_block_till_done()
assert len(discover.mock_calls) == 1
assert len(setup.mock_calls) == 1
assert len(hass.data[tplink.DOMAIN][CONF_LIGHT]) == 1
assert not hass.data[tplink.DOMAIN][CONF_SWITCH]
async def test_configuring_discovery_disabled(hass):
"""Test that discover does not get called when disabled."""
with patch(
"homeassistant.components.tplink.async_setup_entry",
return_value=mock_coro(True),
) as mock_setup, patch(
"homeassistant.components.tplink.common.Discover.discover", return_value=[]
) as discover:
await async_setup_component(
hass, tplink.DOMAIN, {tplink.DOMAIN: {tplink.CONF_DISCOVERY: False}}
)
await hass.async_block_till_done()
assert discover.call_count == 0
assert mock_setup.call_count == 1
async def test_platforms_are_initialized(hass: HomeAssistant):
"""Test that platforms are initialized per configuration array."""
config = {
tplink.DOMAIN: {
CONF_DISCOVERY: False,
CONF_LIGHT: [{CONF_HOST: "123.123.123.123"}],
CONF_SWITCH: [{CONF_HOST: "321.321.321.321"}],
}
}
with patch(
"homeassistant.components.tplink.common.Discover.discover"
) as discover, patch(
"homeassistant.components.tplink.get_static_devices"
) as get_static_devices, patch(
"homeassistant.components.tplink.common.SmartDevice._query_helper"
), patch(
"homeassistant.components.tplink.light.async_setup_entry",
return_value=mock_coro(True),
) as light_setup, patch(
"homeassistant.components.tplink.switch.async_setup_entry",
return_value=mock_coro(True),
) as switch_setup, patch(
"homeassistant.components.tplink.common.SmartPlug.is_dimmable", False
):
light = SmartBulb("123.123.123.123")
switch = SmartPlug("321.321.321.321")
switch.get_sysinfo = MagicMock(return_value=SMARTPLUGSWITCH_DATA["sysinfo"])
switch.get_emeter_realtime = MagicMock(
return_value=EmeterStatus(SMARTPLUGSWITCH_DATA["realtime"])
)
switch.get_emeter_daily = MagicMock(
return_value={int(time.strftime("%e")): 1.123}
)
get_static_devices.return_value = SmartDevices([light], [switch])
# patching is_dimmable is necessray to avoid misdetection as light.
await async_setup_component(hass, tplink.DOMAIN, config)
await hass.async_block_till_done()
assert hass.data.get(tplink.DOMAIN)
assert hass.data[tplink.DOMAIN].get(COORDINATORS)
assert hass.data[tplink.DOMAIN][COORDINATORS].get(switch.mac)
assert isinstance(
hass.data[tplink.DOMAIN][COORDINATORS][switch.mac],
tplink.SmartPlugDataUpdateCoordinator,
)
data = hass.data[tplink.DOMAIN][COORDINATORS][switch.mac].data
assert data[CONF_HOST] == switch.host
assert data[CONF_MAC] == switch.sys_info["mac"]
assert data[CONF_MODEL] == switch.sys_info["model"]
assert data[CONF_SW_VERSION] == switch.sys_info["sw_ver"]
assert data[CONF_ALIAS] == switch.sys_info["alias"]
assert data[CONF_DEVICE_ID] == switch.sys_info["mac"]
emeter_readings = switch.get_emeter_realtime()
assert data[CONF_EMETER_PARAMS][ATTR_VOLTAGE] == round(
float(emeter_readings["voltage"]), 1
)
assert data[CONF_EMETER_PARAMS][ATTR_CURRENT_A] == round(
float(emeter_readings["current"]), 2
)
assert data[CONF_EMETER_PARAMS][ATTR_CURRENT_POWER_W] == round(
float(emeter_readings["power"]), 2
)
assert data[CONF_EMETER_PARAMS][ATTR_TOTAL_ENERGY_KWH] == round(
float(emeter_readings["total"]), 3
)
assert data[CONF_EMETER_PARAMS][ATTR_LAST_RESET][
ATTR_TOTAL_ENERGY_KWH
] == utc_from_timestamp(0)
assert data[CONF_EMETER_PARAMS][ATTR_TODAY_ENERGY_KWH] == 1.123
assert data[CONF_EMETER_PARAMS][ATTR_LAST_RESET][
ATTR_TODAY_ENERGY_KWH
] == datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
assert discover.call_count == 0
assert get_static_devices.call_count == 1
assert light_setup.call_count == 1
assert switch_setup.call_count == 1
async def test_smartplug_without_consumption_sensors(hass: HomeAssistant):
"""Test that platforms are initialized per configuration array."""
config = {
tplink.DOMAIN: {
CONF_DISCOVERY: False,
CONF_SWITCH: [{CONF_HOST: "321.321.321.321"}],
}
}
with patch("homeassistant.components.tplink.common.Discover.discover"), patch(
"homeassistant.components.tplink.get_static_devices"
) as get_static_devices, patch(
"homeassistant.components.tplink.common.SmartDevice._query_helper"
), patch(
"homeassistant.components.tplink.light.async_setup_entry",
return_value=mock_coro(True),
), patch(
"homeassistant.components.tplink.switch.async_setup_entry",
return_value=mock_coro(True),
), patch(
"homeassistant.components.tplink.sensor.SmartPlugSensor.__init__"
) as SmartPlugSensor, patch(
"homeassistant.components.tplink.common.SmartPlug.is_dimmable", False
):
switch = SmartPlug("321.321.321.321")
switch.get_sysinfo = MagicMock(return_value=SMARTPLUGSWITCH_DATA["sysinfo"])
get_static_devices.return_value = SmartDevices([], [switch])
await async_setup_component(hass, tplink.DOMAIN, config)
await hass.async_block_till_done()
assert SmartPlugSensor.call_count == 0
async def test_smartstrip_device(hass: HomeAssistant):
"""Test discover a SmartStrip devices."""
config = {
tplink.DOMAIN: {
CONF_DISCOVERY: True,
}
}
class SmartStrip(smartstrip.SmartStrip):
"""Moked SmartStrip class."""
def get_sysinfo(self):
return SMARTSTRIPWITCH_DATA["sysinfo"]
with patch(
"homeassistant.components.tplink.common.Discover.discover"
) as discover, patch(
"homeassistant.components.tplink.common.SmartDevice._query_helper"
), patch(
"homeassistant.components.tplink.common.SmartPlug.get_sysinfo",
return_value=SMARTSTRIPWITCH_DATA["sysinfo"],
), patch(
"homeassistant.config_entries.ConfigEntries.async_forward_entry_setup"
):
strip = SmartStrip("123.123.123.123")
discover.return_value = {"123.123.123.123": strip}
assert await async_setup_component(hass, tplink.DOMAIN, config)
await hass.async_block_till_done()
assert hass.data.get(tplink.DOMAIN)
assert hass.data[tplink.DOMAIN].get(COORDINATORS)
assert hass.data[tplink.DOMAIN][COORDINATORS].get(strip.mac)
assert isinstance(
hass.data[tplink.DOMAIN][COORDINATORS][strip.mac],
tplink.SmartPlugDataUpdateCoordinator,
)
data = hass.data[tplink.DOMAIN][COORDINATORS][strip.mac].data
assert data[CONF_ALIAS] == strip.sys_info["children"][0]["alias"]
assert data[CONF_DEVICE_ID] == "1"
async def test_no_config_creates_no_entry(hass):
"""Test for when there is no tplink in config."""
with patch(
"homeassistant.components.tplink.async_setup_entry",
return_value=mock_coro(True),
) as mock_setup:
await async_setup_component(hass, tplink.DOMAIN, {})
await hass.async_block_till_done()
assert mock_setup.call_count == 0
async def test_not_ready(hass: HomeAssistant):
"""Test for not ready when configured devices are not available."""
config = {
tplink.DOMAIN: {
CONF_DISCOVERY: False,
CONF_SWITCH: [{CONF_HOST: "321.321.321.321"}],
}
}
with patch("homeassistant.components.tplink.common.Discover.discover"), patch(
"homeassistant.components.tplink.get_static_devices"
) as get_static_devices, patch(
"homeassistant.components.tplink.common.SmartDevice._query_helper"
), patch(
"homeassistant.components.tplink.light.async_setup_entry",
return_value=mock_coro(True),
), patch(
"homeassistant.components.tplink.switch.async_setup_entry",
return_value=mock_coro(True),
), patch(
"homeassistant.components.tplink.common.SmartPlug.is_dimmable", False
):
switch = SmartPlug("321.321.321.321")
switch.get_sysinfo = MagicMock(side_effect=SmartDeviceException())
get_static_devices.return_value = SmartDevices([], [switch])
await async_setup_component(hass, tplink.DOMAIN, config)
await hass.async_block_till_done()
entries = hass.config_entries.async_entries(tplink.DOMAIN)
assert len(entries) == 1
assert entries[0].state is config_entries.ConfigEntryState.SETUP_RETRY
@pytest.mark.parametrize("platform", ["switch", "light"])
async def test_unload(hass, platform):
"""Test that the async_unload_entry works."""
# As we have currently no configuration, we just to pass the domain here.
entry = MockConfigEntry(domain=tplink.DOMAIN)
entry.add_to_hass(hass)
with patch(
"homeassistant.components.tplink.get_static_devices"
) as get_static_devices, patch(
"homeassistant.components.tplink.common.SmartDevice._query_helper"
), patch(
f"homeassistant.components.tplink.{platform}.async_setup_entry",
return_value=mock_coro(True),
) as async_setup_entry:
config = {
tplink.DOMAIN: {
platform: [{CONF_HOST: "123.123.123.123"}],
CONF_DISCOVERY: False,
}
}
light = SmartBulb("123.123.123.123")
switch = SmartPlug("321.321.321.321")
switch.get_sysinfo = MagicMock(return_value=SMARTPLUGSWITCH_DATA["sysinfo"])
switch.get_emeter_realtime = MagicMock(
return_value=EmeterStatus(SMARTPLUGSWITCH_DATA["realtime"])
)
if platform == "light":
get_static_devices.return_value = SmartDevices([light], [])
elif platform == "switch":
get_static_devices.return_value = SmartDevices([], [switch])
assert await async_setup_component(hass, tplink.DOMAIN, config)
await hass.async_block_till_done()
assert len(async_setup_entry.mock_calls) == 1
assert tplink.DOMAIN in hass.data
assert await tplink.async_unload_entry(hass, entry)
assert not hass.data[tplink.DOMAIN]