core/tests/components/tplink/__init__.py

233 lines
7.0 KiB
Python
Raw Normal View History

Add support for automatic discovery of TP-Link switches, bulbs and dimmers (#18091) * {switch,light}.tplink: use deviceid as unique id, fetch name from the device during initialization * raise PlatformNotReady when no device is available * Use mac instead of deviceid * remove name option as obsolete * Add support for configuration flow / integration Allows activating automatic discovery of supported devices from the configuration * Fix linting, update requirements_all.txt * start cleaning up tplink component based on feedback * add device info, improve config handling * Allow overriding detected devices via configuration file * Update requirements.txt * Remove debug logging * make hound happy * Avoid I/O during init and simplify the code, remove remains of leds_on * Fix issues based on feedback, use consistent quotation marks for device info * add async_setup_platform emiting a deprecation warning * Avoid blocking the I/O, check for None on features * handle some Martin's comments, schema-validation is still missing * use async_create_task instead of async_add_job, let core validate the schema * simplify configuration handling by storing the configuration data separately from initialized instances * add default values to schema, make hound happy * with defaults set by schema, simplify the checks. add async_unload_entry * Use constant for data structure access * REWORD add a short note about async_unload_entry * handle feedback from Martin, config_data is checked against Noneness * use pop to remove the domain on unload * First steps to add tests for the new tplink component * embed platforms under the component directory * Fix tests by mocking the pyhs100 internals * Fix linting * Test against multiple instances of devices, tidy up * (hopefully) final linting round * Add pyHS100 to test requirements * log always the warnings occured during an update to make them easy to see * revert back the warning behavior (requirement for silver level in IQS) * Unload only when an entry is being loaded and add tests for that Thanks @MartinHjelmare for pointing this out! * Fix linting * Bump the upstream lib, fixes most prominently the HSV setting on bulbs * Test unloading for all platforms, clear the data storage instead of popping it out, making it possible to reconfigure after removal without restarting hass first * Use class variables instead of instance variables for bulb states, required for HS220 * Use new-style format string * Fix indenting, uppercase the mock constant * Run black on test_init, hopefully that will finally fix the weird formatting (pycharm, pylint and hound seems to have different opinions...)
2019-02-21 19:29:07 +00:00
"""Tests for the TP-Link component."""
from unittest.mock import AsyncMock, MagicMock, patch
from kasa import (
SmartBulb,
SmartDevice,
SmartDimmer,
SmartLightStrip,
SmartPlug,
SmartStrip,
)
from kasa.exceptions import SmartDeviceException
from kasa.protocol import TPLinkSmartHomeProtocol
2022-02-06 22:50:44 +00:00
from homeassistant.components.tplink import CONF_HOST
from homeassistant.components.tplink.const import DOMAIN
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
MODULE = "homeassistant.components.tplink"
MODULE_CONFIG_FLOW = "homeassistant.components.tplink.config_flow"
IP_ADDRESS = "127.0.0.1"
ALIAS = "My Bulb"
MODEL = "HS100"
MAC_ADDRESS = "aa:bb:cc:dd:ee:ff"
DEFAULT_ENTRY_TITLE = f"{ALIAS} {MODEL}"
def _mock_protocol() -> TPLinkSmartHomeProtocol:
protocol = MagicMock(auto_spec=TPLinkSmartHomeProtocol)
protocol.close = AsyncMock()
return protocol
def _mocked_bulb() -> SmartBulb:
bulb = MagicMock(auto_spec=SmartBulb, name="Mocked bulb")
bulb.update = AsyncMock()
bulb.mac = MAC_ADDRESS
bulb.alias = ALIAS
bulb.model = MODEL
bulb.host = IP_ADDRESS
bulb.brightness = 50
bulb.color_temp = 4000
bulb.is_color = True
bulb.is_strip = False
bulb.is_plug = False
bulb.is_dimmer = False
bulb.is_light_strip = False
bulb.has_effects = False
bulb.effect = None
bulb.effect_list = None
bulb.hsv = (10, 30, 5)
bulb.device_id = MAC_ADDRESS
bulb.valid_temperature_range.min = 4000
bulb.valid_temperature_range.max = 9000
bulb.hw_info = {"sw_ver": "1.0.0", "hw_ver": "1.0.0"}
bulb.turn_off = AsyncMock()
bulb.turn_on = AsyncMock()
bulb.set_brightness = AsyncMock()
bulb.set_hsv = AsyncMock()
bulb.set_color_temp = AsyncMock()
bulb.protocol = _mock_protocol()
return bulb
class MockedSmartLightStrip(SmartLightStrip):
"""Mock a SmartLightStrip."""
def __new__(cls, *args, **kwargs):
"""Mock a SmartLightStrip that will pass an isinstance check."""
return MagicMock(spec=cls)
def _mocked_smart_light_strip() -> SmartLightStrip:
strip = MockedSmartLightStrip()
strip.update = AsyncMock()
strip.mac = MAC_ADDRESS
strip.alias = ALIAS
strip.model = MODEL
strip.host = IP_ADDRESS
strip.brightness = 50
strip.color_temp = 4000
strip.is_color = True
strip.is_strip = False
strip.is_plug = False
strip.is_dimmer = False
strip.is_light_strip = True
strip.has_effects = True
strip.effect = {"name": "Effect1", "enable": 1}
strip.effect_list = ["Effect1", "Effect2"]
strip.hsv = (10, 30, 5)
strip.device_id = MAC_ADDRESS
strip.valid_temperature_range.min = 4000
strip.valid_temperature_range.max = 9000
strip.hw_info = {"sw_ver": "1.0.0", "hw_ver": "1.0.0"}
strip.turn_off = AsyncMock()
strip.turn_on = AsyncMock()
strip.set_brightness = AsyncMock()
strip.set_hsv = AsyncMock()
strip.set_color_temp = AsyncMock()
strip.set_effect = AsyncMock()
strip.set_custom_effect = AsyncMock()
strip.protocol = _mock_protocol()
return strip
def _mocked_dimmer() -> SmartDimmer:
dimmer = MagicMock(auto_spec=SmartDimmer, name="Mocked dimmer")
dimmer.update = AsyncMock()
dimmer.mac = MAC_ADDRESS
dimmer.alias = "My Dimmer"
dimmer.model = MODEL
dimmer.host = IP_ADDRESS
dimmer.brightness = 50
dimmer.color_temp = 4000
dimmer.is_color = True
dimmer.is_strip = False
dimmer.is_plug = False
dimmer.is_dimmer = True
dimmer.is_light_strip = False
dimmer.effect = None
dimmer.effect_list = None
dimmer.hsv = (10, 30, 5)
dimmer.device_id = MAC_ADDRESS
dimmer.valid_temperature_range.min = 4000
dimmer.valid_temperature_range.max = 9000
dimmer.hw_info = {"sw_ver": "1.0.0", "hw_ver": "1.0.0"}
dimmer.turn_off = AsyncMock()
dimmer.turn_on = AsyncMock()
dimmer.set_brightness = AsyncMock()
dimmer.set_hsv = AsyncMock()
dimmer.set_color_temp = AsyncMock()
dimmer.set_led = AsyncMock()
dimmer.protocol = _mock_protocol()
return dimmer
def _mocked_plug() -> SmartPlug:
plug = MagicMock(auto_spec=SmartPlug, name="Mocked plug")
plug.update = AsyncMock()
plug.mac = MAC_ADDRESS
plug.alias = "My Plug"
plug.model = MODEL
plug.host = IP_ADDRESS
plug.is_light_strip = False
plug.is_bulb = False
plug.is_dimmer = False
plug.is_strip = False
plug.is_plug = True
plug.device_id = MAC_ADDRESS
plug.hw_info = {"sw_ver": "1.0.0", "hw_ver": "1.0.0"}
plug.turn_off = AsyncMock()
plug.turn_on = AsyncMock()
plug.set_led = AsyncMock()
plug.protocol = _mock_protocol()
return plug
def _mocked_strip() -> SmartStrip:
strip = MagicMock(auto_spec=SmartStrip, name="Mocked strip")
strip.update = AsyncMock()
strip.mac = MAC_ADDRESS
strip.alias = "My Strip"
strip.model = MODEL
strip.host = IP_ADDRESS
strip.is_light_strip = False
strip.is_bulb = False
strip.is_dimmer = False
strip.is_strip = True
strip.is_plug = True
strip.device_id = MAC_ADDRESS
strip.hw_info = {"sw_ver": "1.0.0", "hw_ver": "1.0.0"}
strip.turn_off = AsyncMock()
strip.turn_on = AsyncMock()
strip.set_led = AsyncMock()
strip.protocol = _mock_protocol()
plug0 = _mocked_plug()
plug0.alias = "Plug0"
plug0.device_id = "bb:bb:cc:dd:ee:ff_PLUG0DEVICEID"
plug0.mac = "bb:bb:cc:dd:ee:ff"
plug0.is_on = True
plug0.protocol = _mock_protocol()
plug1 = _mocked_plug()
plug1.device_id = "cc:bb:cc:dd:ee:ff_PLUG1DEVICEID"
plug1.mac = "cc:bb:cc:dd:ee:ff"
plug1.alias = "Plug1"
plug1.protocol = _mock_protocol()
plug1.is_on = False
strip.children = [plug0, plug1]
return strip
def _patch_discovery(device=None, no_device=False):
async def _discovery(*args, **kwargs):
if no_device:
return {}
return {IP_ADDRESS: _mocked_bulb()}
return patch("homeassistant.components.tplink.Discover.discover", new=_discovery)
def _patch_single_discovery(device=None, no_device=False):
async def _discover_single(*_):
if no_device:
raise SmartDeviceException
return device if device else _mocked_bulb()
return patch(
"homeassistant.components.tplink.Discover.discover_single", new=_discover_single
)
2022-02-06 22:50:44 +00:00
async def initialize_config_entry_for_device(
hass: HomeAssistant, dev: SmartDevice
) -> MockConfigEntry:
"""Create a mocked configuration entry for the given device.
Note, the rest of the tests should probably be converted over to use this
instead of repeating the initialization routine for each test separately
"""
config_entry = MockConfigEntry(
title="TP-Link", domain=DOMAIN, unique_id=dev.mac, data={CONF_HOST: dev.host}
)
config_entry.add_to_hass(hass)
with _patch_discovery(device=dev), _patch_single_discovery(device=dev):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
return config_entry