From 13b66e2ccf612c0703278e50573ae018bcad16f9 Mon Sep 17 00:00:00 2001 From: petretiandrea Date: Sat, 6 Apr 2024 12:12:39 +0200 Subject: [PATCH] added more tests and diagnostic feature --- custom_components/tapo/__init__.py | 1 - custom_components/tapo/config_flow.py | 33 +- custom_components/tapo/const.py | 13 +- custom_components/tapo/diagnostics.py | 27 ++ custom_components/tapo/discovery.py | 12 +- custom_components/tapo/hass_tapo.py | 1 + custom_components/tapo/hub/hass_tapo_hub.py | 1 - custom_components/tapo/hub/siren.py | 2 +- custom_components/tapo/light.py | 9 +- custom_components/tapo/manifest.json | 2 +- custom_components/tapo/sensors/__init__.py | 2 +- custom_components/tapo/switch.py | 2 +- requirements_dev.txt | 2 +- tests/conftest.py | 315 +++++++++--------- tests/fixtures/bulb.json | 148 -------- tests/fixtures/discovery.json | 2 +- tests/fixtures/hub.json | 171 ---------- tests/fixtures/ledstrip.json | 186 ----------- tests/fixtures/plug.json | 57 ---- tests/fixtures/plug_emeter.json | 137 -------- tests/fixtures/plug_strip.json | 290 ---------------- tests/tapo_mock_helper.py | 34 -- tests/test_binary_sensor.py | 4 +- tests/test_light.py | 18 +- tests/test_sensor.py | 2 +- tests/test_siren.py | 8 +- tests/test_switch.py | 25 +- tests/unit/hub/conftest.py | 7 + tests/unit/hub/test_binary_sensor.py | 31 +- tests/unit/hub/test_climate.py | 156 +++------ tests/unit/hub/test_number.py | 120 +++---- tests/unit/hub/test_sensor.py | 90 ++--- tests/unit/hub/test_switch.py | 161 +++------ tests/unit/hub/test_tapo_hub.py | 200 +++++------ .../hub/test_tapo_hub_child_coordinator.py | 248 +++++++------- tests/unit/test_const.py | 16 - 36 files changed, 669 insertions(+), 1864 deletions(-) create mode 100644 custom_components/tapo/diagnostics.py delete mode 100644 tests/fixtures/bulb.json delete mode 100644 tests/fixtures/hub.json delete mode 100644 tests/fixtures/ledstrip.json delete mode 100644 tests/fixtures/plug.json delete mode 100644 tests/fixtures/plug_emeter.json delete mode 100644 tests/fixtures/plug_strip.json delete mode 100644 tests/tapo_mock_helper.py create mode 100644 tests/unit/hub/conftest.py delete mode 100644 tests/unit/test_const.py diff --git a/custom_components/tapo/__init__.py b/custom_components/tapo/__init__.py index 348cd15..59555e7 100755 --- a/custom_components/tapo/__init__.py +++ b/custom_components/tapo/__init__.py @@ -62,7 +62,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): except DeviceNotSupported as error: raise error except Exception as error: - print(error) raise ConfigEntryNotReady from error diff --git a/custom_components/tapo/config_flow.py b/custom_components/tapo/config_flow.py index dcacc39..1b21bae 100755 --- a/custom_components/tapo/config_flow.py +++ b/custom_components/tapo/config_flow.py @@ -115,7 +115,7 @@ async def async_step_dhcp( ) -> data_entry_flow.FlowResult: """Handle discovery via dhcp.""" mac_address = dr.format_mac(discovery_info.macaddress) - if discovered_device := await discover_tapo_device(self.hass, mac_address): + if discovered_device := await discover_tapo_device(discovery_info.ip): return await self._async_handle_discovery( discovery_info.ip, mac_address, discovered_device ) @@ -140,7 +140,7 @@ async def async_step_user( if user_input is not None: try: - device = await self._async_get_device_info(user_input) + device = await self._async_get_device(user_input) await self.async_set_unique_id(dr.format_mac(device.mac)) self._abort_if_unique_id_configured() self._async_abort_entries_match({CONF_HOST: device.host}) @@ -228,7 +228,7 @@ async def async_step_discovery_auth_confirm( if user_input: try: - device = await self._async_get_device_info_from_discovered( + device = await self._async_get_device_from_discovered( self._discovered_info, user_input ) await self.async_set_unique_id(dr.format_mac(device.mac)) @@ -288,24 +288,31 @@ async def _async_create_config_entry_from_device_info( }, ) - async def _async_get_device_info_from_discovered( + async def _async_get_device_from_discovered( self, discovered: DiscoveredDevice, config: dict[str, Any] ) -> TapoDevice: - return await self._async_get_device_info(config | {CONF_HOST: discovered.ip}) + return await self._async_get_device(config | {CONF_HOST: discovered.ip}, discovered) - async def _async_get_device_info(self, config: dict[str, Any]) -> TapoDevice: + async def _async_get_device( + self, + config: dict[str, Any], + discovered_device: DiscoveredDevice | None = None, + ) -> TapoDevice: if not config[CONF_HOST]: raise InvalidHost try: session = create_aiohttp_session(self.hass) credential = AuthCredential(config[CONF_USERNAME], config[CONF_PASSWORD]) - host, port = get_host_port(config[CONF_HOST]) - config = DeviceConnectConfiguration( - credentials=credential, - host=host, - port=port, - ) - device = await connect(config=config, session=session) + if discovered_device is None: + host, port = get_host_port(config[CONF_HOST]) + config = DeviceConnectConfiguration( + credentials=credential, + host=host, + port=port, + ) + device = await connect(config=config, session=session) + else: + device = await discovered_device.get_tapo_device(credential, session) await device.update() return device except TapoException as error: diff --git a/custom_components/tapo/const.py b/custom_components/tapo/const.py index af8089a..ff35ae1 100755 --- a/custom_components/tapo/const.py +++ b/custom_components/tapo/const.py @@ -97,15 +97,4 @@ If you have any issues with this you need to open an issue here: {ISSUE_URL} ------------------------------------------------------------------- -""" - -#TapoDevice = Union[LightDevice, PlugDevice, LedStripDevice, HubDevice, PowerStripDevice] - - -class Component(Enum): - COLOR_TEMPERATURE = "color_temperature" - BRIGHTNESS = "brightness" - COLOR = "color" - LIGHT_STRIP = "light_strip" - LIGHT_STRIP_EFFECTS = "light_strip_lighting_effect" - ALARM = "alarm" +""" \ No newline at end of file diff --git a/custom_components/tapo/diagnostics.py b/custom_components/tapo/diagnostics.py new file mode 100644 index 0000000..527aa12 --- /dev/null +++ b/custom_components/tapo/diagnostics.py @@ -0,0 +1,27 @@ +from typing import Any, cast + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import format_mac + +from . import HassTapoDeviceData +from .const import DOMAIN + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + data = cast(HassTapoDeviceData, hass.data[DOMAIN][entry.entry_id]) + oui = format_mac(data.coordinator.device.mac)[:8].upper() + children_diagnostics = [] + if len(data.child_coordinators) > 0: + children_diagnostics = [ + { 'nickname': child.device.nickname, 'raw_state': child.device.raw_state } + for child in data.child_coordinators + ] + return { + 'oui': oui, + 'protocol_name': data.coordinator.device.protocol_version, + 'raw_state': data.coordinator.device.raw_state, + 'children': children_diagnostics + } diff --git a/custom_components/tapo/discovery.py b/custom_components/tapo/discovery.py index 826c707..d152948 100644 --- a/custom_components/tapo/discovery.py +++ b/custom_components/tapo/discovery.py @@ -1,4 +1,5 @@ import asyncio +import logging from itertools import chain from typing import Optional @@ -25,9 +26,10 @@ async def discovery_tapo_devices(hass: HomeAssistant) -> dict[str, DiscoveredDev async def discover_tapo_device( - hass: HomeAssistant, mac: str + ip: str, ) -> Optional[DiscoveredDevice]: - found_devices = await discovery_tapo_devices(hass) - return next( - filter(lambda x: dr.format_mac(x.mac) == mac, found_devices.values()), None - ) + try: + return await TapoDiscovery.single_scan(ip, DISCOVERY_TIMEOUT) + except: + logging.error("Faild during discovery of device with ip {}", ip) + return None diff --git a/custom_components/tapo/hass_tapo.py b/custom_components/tapo/hass_tapo.py index 9ac5ea8..85b4b51 100644 --- a/custom_components/tapo/hass_tapo.py +++ b/custom_components/tapo/hass_tapo.py @@ -50,6 +50,7 @@ async def _initialize_device( child_coordinators=[], device=device, ) + await hass.config_entries.async_forward_entry_setups(self.entry, PLATFORMS) return True diff --git a/custom_components/tapo/hub/hass_tapo_hub.py b/custom_components/tapo/hub/hass_tapo_hub.py index 9f10916..4bee491 100644 --- a/custom_components/tapo/hub/hass_tapo_hub.py +++ b/custom_components/tapo/hub/hass_tapo_hub.py @@ -50,7 +50,6 @@ async def initialize_hub(self, hass: HomeAssistant): child_coordinators = await self.setup_children( hass, registry, self.hub.children, polling_rate ) - print("Coordinators", child_coordinators) hass.data[DOMAIN][self.entry.entry_id] = HassTapoDeviceData( coordinator=hub_coordinator, config_entry_update_unsub=self.entry.add_update_listener( diff --git a/custom_components/tapo/hub/siren.py b/custom_components/tapo/hub/siren.py index b87e024..c27008c 100644 --- a/custom_components/tapo/hub/siren.py +++ b/custom_components/tapo/hub/siren.py @@ -23,7 +23,7 @@ async def async_setup_entry( ): data = cast(HassTapoDeviceData, hass.data[DOMAIN][entry.entry_id]) if isinstance(data.device, TapoHub): - if data.coordinator.device.has_component(AlarmComponent): + if data.coordinator.device.has_alarm: available_tones = ( (await data.device.get_supported_alarm_tones()) .get_or_raise() diff --git a/custom_components/tapo/light.py b/custom_components/tapo/light.py index 53024e6..e3bb403 100755 --- a/custom_components/tapo/light.py +++ b/custom_components/tapo/light.py @@ -16,11 +16,9 @@ color_temperature_kelvin_to_mired as kelvin_to_mired, ) from plugp100.api.light_effect_preset import LightEffectPreset -from plugp100.new.components.light_component import LightComponent from plugp100.new.components.light_effect_component import LightEffectComponent from plugp100.new.tapobulb import TapoBulb -from custom_components.tapo.const import Component from custom_components.tapo.const import DOMAIN from custom_components.tapo.coordinators import HassTapoDeviceData, TapoDataCoordinator from custom_components.tapo.entity import CoordinatedTapoEntity @@ -194,14 +192,13 @@ async def _change_brightness(self, new_brightness, apply_to_effect: str = None): ).get_or_raise() -# TODO: split to more componenets on TapoBulb library def _components_to_color_modes(device: TapoBulb) -> set[ColorMode]: color_modes = [ColorMode.ONOFF] - if device.has_component(LightComponent) and device.components.has(Component.COLOR_TEMPERATURE.value): + if device.is_color_temperature: color_modes.append(ColorMode.COLOR_TEMP) - if device.has_component(LightComponent) and device.components.has(Component.BRIGHTNESS.value): + if device.is_brightness: color_modes.append(ColorMode.BRIGHTNESS) - if device.has_component(LightComponent) and device.components.has(Component.COLOR.value): + if device.is_color: color_modes.append(ColorMode.HS) return set(color_modes) diff --git a/custom_components/tapo/manifest.json b/custom_components/tapo/manifest.json index dece10c..6981567 100755 --- a/custom_components/tapo/manifest.json +++ b/custom_components/tapo/manifest.json @@ -6,7 +6,7 @@ "iot_class": "local_polling", "documentation": "https://github.com/petretiandrea/home-assistant-tapo-p100", "issue_tracker": "https://github.com/petretiandrea/home-assistant-tapo-p100/issues", - "requirements": ["plugp100==5.0.0.dev2"], + "requirements": ["plugp100==5.0.0.dev4"], "dependencies": ["network"], "dhcp": [ { diff --git a/custom_components/tapo/sensors/__init__.py b/custom_components/tapo/sensors/__init__.py index bd360b5..a091fe4 100644 --- a/custom_components/tapo/sensors/__init__.py +++ b/custom_components/tapo/sensors/__init__.py @@ -116,4 +116,4 @@ def get_config(self) -> SensorConfig: def get_value(self, coordinator: TapoDataCoordinator) -> StateType: if energy := coordinator.device.get_component(EnergyComponent): return energy.energy_info.month_runtime if energy.energy_info else None - return None + return None \ No newline at end of file diff --git a/custom_components/tapo/switch.py b/custom_components/tapo/switch.py index c72c803..82ec1ee 100755 --- a/custom_components/tapo/switch.py +++ b/custom_components/tapo/switch.py @@ -41,7 +41,7 @@ async def async_setup_device_switch( class TapoPlugEntity(CoordinatedTapoEntity, SwitchEntity): _attr_device_class = SwitchDeviceClass.OUTLET - _attr_has_entity_name = True + _attr_has_entity_name = False def __init__( self, diff --git a/requirements_dev.txt b/requirements_dev.txt index 14410f7..96a9fab 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,5 +1,5 @@ homeassistant==2024.2.0 -plugp100==5.0.0.dev2 +plugp100==5.0.0.dev4 pre-commit==3.3.3 reorder-python-imports==3.10.0 flake8==6.1.0 diff --git a/tests/conftest.py b/tests/conftest.py index 8e56cad..80d1e9b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,43 +5,37 @@ from unittest.mock import patch import pytest -from plugp100.new.components.energy_component import EnergyComponent -from plugp100.new.tapodevice import TapoDevice -from plugp100.new.tapoplug import TapoPlug - -from custom_components.tapo.const import CONF_HOST -from custom_components.tapo.const import CONF_PASSWORD -from custom_components.tapo.const import CONF_USERNAME -from custom_components.tapo.const import DOMAIN from homeassistant.const import CONF_SCAN_INTERVAL from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component +from plugp100.api.light_effect_preset import LightEffectPreset from plugp100.common.functional.tri import Success from plugp100.common.functional.tri import Try from plugp100.discovery.discovered_device import DiscoveredDevice +from plugp100.new.child.tapostripsocket import TapoStripSocket +from plugp100.new.components.energy_component import EnergyComponent +from plugp100.new.components.light_component import HS, LightComponent +from plugp100.new.components.light_effect_component import LightEffectComponent +from plugp100.new.components.overheat_component import OverheatComponent +from plugp100.new.tapobulb import TapoBulb +from plugp100.new.tapodevice import TapoDevice +from plugp100.new.tapohub import TapoHub +from plugp100.new.tapoplug import TapoPlug from plugp100.responses.alarm_type_list import AlarmTypeList -from plugp100.responses.child_device_list import ChildDeviceList -from plugp100.responses.child_device_list import PowerStripChild from plugp100.responses.components import Components -from plugp100.responses.device_state import DeviceInfo -from plugp100.responses.device_state import HubDeviceState -from plugp100.responses.device_state import LedStripDeviceState -from plugp100.responses.device_state import LightDeviceState -from plugp100.responses.device_state import PlugDeviceState -from plugp100.responses.energy_info import EnergyInfo -from plugp100.responses.power_info import PowerInfo -from plugp100.responses.tapo_response import TapoResponse -from pytest_homeassistant_custom_component.common import load_fixture from pytest_homeassistant_custom_component.common import MockConfigEntry +from pytest_homeassistant_custom_component.common import load_fixture -from .tapo_mock_helper import tapo_response_child_of -from .tapo_mock_helper import tapo_response_of +from custom_components.tapo.const import CONF_HOST +from custom_components.tapo.const import CONF_PASSWORD +from custom_components.tapo.const import CONF_USERNAME +from custom_components.tapo.const import DOMAIN pytest_plugins = ("pytest_homeassistant_custom_component",) IP_ADDRESS = "1.2.3.4" -MAC_ADDRESS = "aa:bb:cc:dd:ee:ff" +MAC_ADDRESS = "1a:22:33:b4:c5:66" @pytest.fixture(autouse=True) @@ -56,31 +50,19 @@ def expected_lingering_tasks() -> bool: @pytest.fixture() def mock_discovery(): - (discovered_device, device_info) = mock_discovered_device() + discovered_device = mock_discovered_device() + device = _mock_base_device(MagicMock(auto_spec=TapoDevice)) with patch( "custom_components.tapo.discovery_tapo_devices", - AsyncMock(return_value={device_info.mac: device_info}), + AsyncMock(return_value={device.mac: discovered_device}), ): - with patch( - "custom_components.tapo.config_flow.TapoConfigFlow._async_get_device_info", - AsyncMock(return_value=device_info), + with patch.object( + discovered_device, + "get_tapo_device", + side_effect=AsyncMock(return_value=device), ): yield discovered_device - -def fixture_tapo_map(resource: str) -> dict[str, Try[TapoResponse]]: - elems = json.loads(load_fixture(resource)) - return {k: tapo_response_of(elems[k]) for k in elems.keys()} - - -def fixture_tapo_response(resource: str) -> Try[TapoResponse]: - return tapo_response_of(json.loads(load_fixture(resource))) - - -def fixture_tapo_response_child(resource: str) -> Try[TapoResponse]: - return tapo_response_child_of(json.loads(load_fixture(resource))) - - async def setup_platform( hass: HomeAssistant, device: TapoDevice, platforms: list[str] ) -> MockConfigEntry: @@ -99,7 +81,7 @@ async def setup_platform( ) config_entry.add_to_hass(hass) with patch( - "custom_components.tapo.hass_tapo.connect", return_value=AsyncMock(device) + "custom_components.tapo.hass_tapo.connect", AsyncMock(return_value=device) ): with patch.object(hass.config_entries, "async_forward_entry_setup"): assert ( @@ -117,128 +99,108 @@ async def setup_platform( return config_entry -def mock_discovered_device() -> [DiscoveredDevice, DeviceInfo]: - response = fixture_tapo_map("bulb.json") - state = ( - response.get("get_device_info") - .flat_map(lambda x: LightDeviceState.try_from_json(x.result)) - .get_or_raise() - .info - ) - return [ - DiscoveredDevice.from_dict(json.loads(load_fixture("discovery.json"))), - state, - ] - +def mock_discovered_device() -> DiscoveredDevice: + return DiscoveredDevice.from_dict(json.loads(load_fixture("discovery.json"))) def mock_plug(with_emeter: bool = False) -> MagicMock: - response = fixture_tapo_map("plug.json" if not with_emeter else "plug_emeter.json") - state = response.get("get_device_info").flat_map( - lambda x: PlugDeviceState.try_from_json(x.result) - ) - components = response.get("component_nego").map( - lambda x: Components.try_from_json(x.result) - ) - device = MagicMock(auto_spec=TapoPlug, name="Mocked plug device") + device = _mock_base_device(MagicMock(auto_spec=TapoPlug, name="Mocked plug device")) device.turn_on = AsyncMock(return_value=Success(True)) device.turn_off = AsyncMock(return_value=Success(True)) - #device.get_state = AsyncMock(return_value=state) - device._negotiate_components = AsyncMock(return_value=components) - device.update = AsyncMock() + device.is_on = True + device.is_strip = False + device.model = "P100" device.__class__ = TapoPlug if with_emeter: emeter = MagicMock(EnergyComponent(MagicMock())) - emeter.update = AsyncMock() - emeter.power_info = response.get("get_current_power").map(lambda x: PowerInfo(x.result)).get_or_raise().current_power - emeter.energy_info = response.get("get_energy_usage").map(lambda x: EnergyInfo(x.result)).get_or_raise().current_power + emeter.update = AsyncMock(return_value=None) + emeter.energy_info.today_runtime = 3 + emeter.energy_info.month_runtime = 19742 + emeter.energy_info.today_energy = 0 + emeter.energy_info.month_energy = 1421 + emeter.energy_info.current_power = 1.2 + emeter.power_info = None + device.add_component(emeter) - device.device_id = state.value.info.device_id return device def mock_hub(with_children: bool = False) -> MagicMock: - response = fixture_tapo_map("hub.json") - state = response.get("get_device_info").flat_map( - lambda x: HubDeviceState.try_from_json(x.result) - ) - components = response.get("component_nego").map( - lambda x: Components.try_from_json(x.result) - ) - device = MagicMock(auto_spec=HubDevice, name="Mocked hub device") + device = _mock_base_device(MagicMock(auto_spec=TapoHub, name="Mocked hub device")) device.turn_alarm_on = AsyncMock(return_value=Success(True)) device.turn_alarm_off = AsyncMock(return_value=Success(True)) - device.get_state = AsyncMock(return_value=state) - device.get_component_negotiation = AsyncMock(return_value=components) - device.get_children = AsyncMock(return_value=Success(ChildDeviceList([], 0, 0))) + device.has_alarm = True + device.is_alarm_on = True device.subscribe_device_association = MagicMock() device.get_supported_alarm_tones = AsyncMock( return_value=Success(AlarmTypeList(["test_tone"])) ) - if with_children: - children = response.get("get_child_device_list").map( - lambda x: ChildDeviceList.try_from_json(**x.result) - ) - device.get_children = AsyncMock(return_value=children) - device.__class__ = HubDevice - device.device_id = state.value.info.device_id + device.children = [] + device.__class__ = TapoHub return device -def mock_bulb(components_to_exclude: list[str] = []) -> MagicMock: - response = fixture_tapo_map("bulb.json") - state = response.get("get_device_info").flat_map( - lambda x: LightDeviceState.try_from_json(x.result) - ) - components = ( - response.get("component_nego") - .map(lambda x: Components.try_from_json(x.result)) - .map(lambda x: exclude_components(x, components_to_exclude)) - ) - device = MagicMock(auto_spec=LightDevice, name="Mocked bulb device") - device.on = AsyncMock(return_value=Success(True)) - device.off = AsyncMock(return_value=Success(True)) - device.set_brightness = AsyncMock(return_value=Success(True)) - device.set_hue_saturation = AsyncMock(return_value=Success(True)) - device.set_color_temperature = AsyncMock(return_value=Success(True)) - device.get_state = AsyncMock(return_value=state) - device.get_component_negotiation = AsyncMock(return_value=components) - device.__class__ = LightDevice - - device.device_id = state.value.info.device_id +def mock_bulb(is_color: bool = True) -> MagicMock: + device = _mock_base_device(MagicMock(TapoBulb("", 80, MagicMock()), name="Mocked bulb device")) + device.is_color = is_color + device.is_color_temperature = True + device.is_led_strip = False + device.device_on = True + device.brightness = 100 + device.hs = HS(139, 38) + device.color_temp = 6493 + device.color_temp_range = [2500, 6500] + device.effect = None + device.turn_on = AsyncMock(return_value=Try.of(True)) + device.turn_off = AsyncMock(return_value=Try.of(True)) + device.set_brightness = AsyncMock(return_value=Try.of(True)) + device.set_hue_saturation = AsyncMock(return_value=Try.of(True)) + device.set_color_temperature = AsyncMock(return_value=Try.of(True)) + device.set_light_effect = AsyncMock(return_value=Try.of(True)) + device.set_light_effect_brightness = AsyncMock(return_value=Try.of(True)) + device.__class__ = TapoBulb + return device def mock_led_strip() -> MagicMock: - response = fixture_tapo_map("ledstrip.json") - state = response.get("get_device_info").flat_map( - lambda x: LedStripDeviceState.try_from_json(x.result) - ) - components = response.get("component_nego").map( - lambda x: Components.try_from_json(x.result) - ) - device = MagicMock(auto_spec=LedStripDevice, name="Mocked led strip device") - device.on = AsyncMock(return_value=Success(True)) - device.off = AsyncMock(return_value=Success(True)) - device.set_brightness = AsyncMock(return_value=Success(True)) - device.set_hue_saturation = AsyncMock(return_value=Success(True)) - device.set_color_temperature = AsyncMock(return_value=Success(True)) - device.set_light_effect = AsyncMock(return_value=Success(True)) - device.set_light_effect_brightness = AsyncMock(return_value=Success(True)) - device.get_state = AsyncMock(return_value=state) - device.get_component_negotiation = AsyncMock(return_value=components) - device.__class__ = LedStripDevice - device.device_id = state.value.info.device_id + device = _mock_base_device(MagicMock(auto_spec=TapoBulb, name="Mocked led strip device")) + device.is_color = True + device.is_color_temperature = True + device.is_led_strip = True + device.device_on = True + device.brightness = 100 + device.hs = HS(139, 38) + device.color_temp = 6493 + device.color_temp_range = [2500, 6500] + device.effect = LightEffectPreset.Ocean.to_effect() + device.turn_on = AsyncMock(return_value=Try.of(True)) + device.turn_off = AsyncMock(return_value=Try.of(True)) + device.set_brightness = AsyncMock(return_value=Try.of(True)) + device.set_hue_saturation = AsyncMock(return_value=Try.of(True)) + device.set_color_temperature = AsyncMock(return_value=Try.of(True)) + device.set_light_effect = AsyncMock(return_value=Try.of(True)) + device.set_light_effect_brightness = AsyncMock(return_value=Try.of(True)) + device.__class__ = TapoBulb + + light_component = MagicMock(LightComponent(MagicMock()), name="Light component") + light_component.update = AsyncMock(return_value=None) + effect_component = MagicMock(LightEffectComponent(MagicMock()), name = "Light effect component") + effect_component.update = AsyncMock(return_value=None) + device.add_component(light_component) + device.add_component(effect_component) return device -async def extract_entity_id(device: TapoDevice, platform: str, postfix: str = ""): - nickname = ( - (await device.get_state()) - .map(lambda x: x.info.nickname if x.info.nickname != "" else x.info.model) - .get_or_raise() - ) +def _mock_overheat(device) -> MagicMock: + overheat = MagicMock(OverheatComponent()) + overheat.update = AsyncMock(return_value=None) + overheat.overheated = False + device.add_component(overheat) + return device +async def extract_entity_id(device: TapoDevice, platform: str, postfix: str = ""): + nickname = device.nickname return platform + "." + (nickname + " " + postfix).strip().lower().replace(" ", "_") @@ -255,26 +217,69 @@ def exclude_components(components: Components, to_exclude: list[str]) -> Compone def mock_plug_strip() -> MagicMock: - response = fixture_tapo_map("plug_strip.json") - state = response.get("get_device_info").flat_map( - lambda x: PlugDeviceState.try_from_json(x.result) - ) - components = response.get("component_nego").map( - lambda x: Components.try_from_json(x.result) - ) - device = MagicMock(auto_spec=PowerStripDevice, name="Mocked plug strip device") - device.get_state = AsyncMock(return_value=state) - device.get_component_negotiation = AsyncMock(return_value=components) - device.on = AsyncMock(return_value=Success(True)) - device.off = AsyncMock(return_value=Success(True)) - children = ( - response.get("get_child_device_list") - .map(lambda x: ChildDeviceList.try_from_json(**x.result)) - .map(lambda sub: sub.get_children(lambda x: PowerStripChild.try_from_json(**x))) - .map(lambda x: {child.device_id: child for child in x}) - ) - device.get_children = AsyncMock(return_value=children) - device.__class__ = PowerStripDevice - device.device_id = state.value.info.device_id + device = _mock_base_device(MagicMock(auto_spec=TapoPlug, name="Mocked plug strip device")) + device.turn_on = AsyncMock(return_value=Success(True)) + device.turn_off = AsyncMock(return_value=Success(True)) + device.is_on = True + device.is_strip = True + device.model = "P300" + device.__class__ = TapoPlug + _mock_overheat(device) + + sockets = [] + for i in range(0, 3): + sock = _mock_base_device(MagicMock(auto_spec=TapoStripSocket, name=f"Mocked socket {i}")) + sock.is_on = True + sock.turn_on = AsyncMock(return_value=Success(True)) + sock.turn_off = AsyncMock(return_value=Success(True)) + sock.device_id = f"123{i}" + sock.parent_device_id = "123" + sock.nickname = f"Nickname{i}" + sockets.append(sock) + + device.sockets = sockets + + return device +def _mock_base_device(device: MagicMock) -> MagicMock: + device.host = "1.2.3.4" + device.nickname = "Nickname" + device.device_id = "123" + device.turn_on = AsyncMock(return_value=Success(True)) + device.turn_off = AsyncMock(return_value=Success(True)) + device.is_on = True + device.model = "T100" + device.firmware_version = "1.0.0" + device.device_info.hardware_version = "1.0.0" + device.mac = "1A-22-33-B4-C5-66" + device.update = AsyncMock(return_value=None) + device.registry = MockComponentsRegistry() + device.add_component = MagicMock(side_effect=device.registry.add_component) + device.get_component = MagicMock(side_effect=device.registry.get_component) + device.has_component = MagicMock(side_effect=device.registry.has_component) + return _mock_overheat(device) + +def _mock_hub_child_device(device: MagicMock) -> MagicMock: + device = _mock_base_device(device) + device.parent_device_id = "parent_123" return device + +class MockComponentsRegistry: + + def __init__(self): + self._cs =[] + + def add_component(self, c: MagicMock): + self._cs.append(c) + + def has_component(self, x) -> bool: + for c in self._cs: + if x == c.__class__: + return True + return False + + def get_component(self, x) -> MagicMock | None: + for c in self._cs: + if x == c.__class__: + return c + return None diff --git a/tests/fixtures/bulb.json b/tests/fixtures/bulb.json deleted file mode 100644 index 3ae9384..0000000 --- a/tests/fixtures/bulb.json +++ /dev/null @@ -1,148 +0,0 @@ -{ - "component_nego": { - "component_list": [ - { - "id": "device", - "ver_code": 2 - }, - { - "id": "firmware", - "ver_code": 2 - }, - { - "id": "quick_setup", - "ver_code": 3 - }, - { - "id": "inherit", - "ver_code": 1 - }, - { - "id": "time", - "ver_code": 1 - }, - { - "id": "wireless", - "ver_code": 1 - }, - { - "id": "schedule", - "ver_code": 2 - }, - { - "id": "countdown", - "ver_code": 2 - }, - { - "id": "antitheft", - "ver_code": 1 - }, - { - "id": "account", - "ver_code": 1 - }, - { - "id": "synchronize", - "ver_code": 1 - }, - { - "id": "sunrise_sunset", - "ver_code": 1 - }, - { - "id": "cloud_connect", - "ver_code": 1 - }, - { - "id": "default_states", - "ver_code": 1 - }, - { - "id": "preset", - "ver_code": 1 - }, - { - "id": "brightness", - "ver_code": 1 - }, - { - "id": "color", - "ver_code": 1 - }, - { - "id": "color_temperature", - "ver_code": 1 - }, - { - "id": "auto_light", - "ver_code": 1 - }, - { - "id": "on_off_gradually", - "ver_code": 1 - }, - { - "id": "device_local_time", - "ver_code": 1 - }, - { - "id": "light_effect", - "ver_code": 1 - }, - { - "id": "iot_cloud", - "ver_code": 1 - }, - { - "id": "bulb_quick_control", - "ver_code": 1 - }, - { - "id": "localSmart", - "ver_code": 1 - } - ] - }, - "get_device_info": { - "device_id": "device_id_123", - "fw_ver": "1.1.0 Build 230721 Rel.224802", - "hw_ver": "2.0", - "type": "SMART.TAPOBULB", - "model": "L530", - "hw_id": "FDE1C68674D1535B12A042682B192E4E", - "fw_id": "00000000000000000000000000000000", - "oem_id": "2241AD354FF56159AF1AC4A53B011A5D", - "color_temp_range": [2500, 6500], - "overheated": false, - "ip": "1.2.3.4", - "mac": "aa-bb-cc-dd-ee-ff", - "time_diff": 60, - "ssid": "SG9tZS1OZXR3b3Jr", - "rssi": -63, - "signal_level": 2, - "latitude": 1, - "longitude": 1, - "lang": "en_US", - "avatar": "bulb", - "region": "Europe/Rome", - "specs": "", - "nickname": "TGFtcGFkaW5hIFNtYXJ0", - "has_set_location_info": true, - "device_on": true, - "brightness": 100, - "hue": 139, - "saturation": 78, - "color_temp": 6493, - "dynamic_light_effect_enable": false, - "default_states": { - "re_power_type": "always_on", - "type": "last_states", - "state": { - "brightness": 100, - "hue": 139, - "saturation": 78, - "color_temp": 6493 - } - } - } -} diff --git a/tests/fixtures/discovery.json b/tests/fixtures/discovery.json index 1a608e3..9d28ece 100644 --- a/tests/fixtures/discovery.json +++ b/tests/fixtures/discovery.json @@ -4,7 +4,7 @@ "device_type": "SMART.TAPOBULB", "device_model": "L530E(EU)", "ip": "1.2.3.4", - "mac": "aa-bb-cc-dd-ee-ff", + "mac": "1a-22-33-b4-c5-66", "is_support_iot_cloud": true, "obd_src": "tplink", "factory_default": false, diff --git a/tests/fixtures/hub.json b/tests/fixtures/hub.json deleted file mode 100644 index 6fece6a..0000000 --- a/tests/fixtures/hub.json +++ /dev/null @@ -1,171 +0,0 @@ -{ - "get_device_info": { - "device_id": "802D86324AC15B78B560A284ED9A2E292137268A", - "fw_ver": "1.3.9 Build 230522 Rel.103415", - "hw_ver": "1.0", - "type": "SMART.TAPOHUB", - "model": "H100", - "mac": "78-8C-B5-F7-46-85", - "hw_id": "81C01BC6E4AD1827315AC6A9D81A5132", - "fw_id": "00000000000000000000000000000000", - "oem_id": "D65F1AA7B819559EDF3C5C2BD5957929", - "overheated": false, - "ip": "192.168.1.13", - "time_diff": 60, - "ssid": "SG9tZS1OZXR3b3Jr", - "rssi": -70, - "signal_level": 1, - "in_alarm": false, - "in_alarm_source": "", - "latitude": 437009, - "longitude": 128307, - "lang": "it_IT", - "avatar": "hub", - "region": "Europe/Rome", - "specs": "EU", - "nickname": "U21hcnQgSHVi", - "has_set_location_info": false - }, - "get_child_device_list": { - "child_device_list": [ - { - "parent_device_id": "802D86324AC15B78B560A284ED9A2E292137268A", - "hw_ver": "1.0", - "fw_ver": "1.10.0 Build 220728 Rel.154816", - "device_id": "802E7B27B9B02EE9CC80D45266892785214DC06F", - "mac": "5C628BC0ECA0", - "type": "SMART.TAPOSENSOR", - "model": "S200B", - "hw_id": "9A1EB033BE24834E2A7A6A4E4CF405E8", - "oem_id": "3D8088F225430E1A4D209836F8B29E3A", - "specs": "EU", - "category": "subg.trigger.button", - "bind_count": 7, - "status_follow_edge": false, - "status": "online", - "lastOnboardingTimestamp": 1698964042, - "rssi": -85, - "signal_level": 1, - "jamming_rssi": -120, - "jamming_signal_level": 1, - "at_low_battery": false, - "nickname": "UHVsc2FudGUgaW50ZWxsaWdlbnRl", - "avatar": "button", - "report_interval": 16, - "region": "Europe/Rome" - } - ], - "start_index": 0, - "sum": 1 - }, - "get_device_info_802E7B27B9B02EE9CC80D45266892785214DC06F": { - "parent_device_id": "802D86324AC15B78B560A284ED9A2E292137268A", - "hw_ver": "1.0", - "fw_ver": "1.10.0 Build 220728 Rel.154816", - "device_id": "802E7B27B9B02EE9CC80D45266892785214DC06F", - "mac": "5C628BC0ECA0", - "type": "SMART.TAPOSENSOR", - "model": "S200B", - "hw_id": "9A1EB033BE24834E2A7A6A4E4CF405E8", - "oem_id": "3D8088F225430E1A4D209836F8B29E3A", - "specs": "EU", - "category": "subg.trigger.button", - "bind_count": 7, - "status_follow_edge": false, - "status": "online", - "lastOnboardingTimestamp": 1698964042, - "rssi": -85, - "signal_level": 1, - "jamming_rssi": -120, - "jamming_signal_level": 1, - "at_low_battery": false, - "nickname": "UHVsc2FudGUgaW50ZWxsaWdlbnRl", - "avatar": "button", - "report_interval": 16, - "region": "Europe/Rome" - }, - "component_nego": { - "component_list": [ - { - "id": "device", - "ver_code": 2 - }, - { - "id": "firmware", - "ver_code": 2 - }, - { - "id": "quick_setup", - "ver_code": 3 - }, - { - "id": "inherit", - "ver_code": 1 - }, - { - "id": "time", - "ver_code": 1 - }, - { - "id": "wireless", - "ver_code": 1 - }, - { - "id": "account", - "ver_code": 1 - }, - { - "id": "synchronize", - "ver_code": 1 - }, - { - "id": "sunrise_sunset", - "ver_code": 1 - }, - { - "id": "led", - "ver_code": 1 - }, - { - "id": "cloud_connect", - "ver_code": 1 - }, - { - "id": "iot_cloud", - "ver_code": 1 - }, - { - "id": "child_device", - "ver_code": 1 - }, - { - "id": "child_quick_setup", - "ver_code": 1 - }, - { - "id": "child_inherit", - "ver_code": 1 - }, - { - "id": "control_child", - "ver_code": 1 - }, - { - "id": "alarm", - "ver_code": 1 - }, - { - "id": "device_load", - "ver_code": 1 - }, - { - "id": "device_local_time", - "ver_code": 1 - }, - { - "id": "alarm_logs", - "ver_code": 1 - } - ] - } -} diff --git a/tests/fixtures/ledstrip.json b/tests/fixtures/ledstrip.json deleted file mode 100644 index b323334..0000000 --- a/tests/fixtures/ledstrip.json +++ /dev/null @@ -1,186 +0,0 @@ -{ - "component_nego": { - "component_list": [ - { - "id": "device", - "ver_code": 2 - }, - { - "id": "light_strip", - "ver_code": 1 - }, - { - "id": "light_strip_lighting_effect", - "ver_code": 2 - }, - { - "id": "firmware", - "ver_code": 2 - }, - { - "id": "quick_setup", - "ver_code": 3 - }, - { - "id": "inherit", - "ver_code": 1 - }, - { - "id": "time", - "ver_code": 1 - }, - { - "id": "wireless", - "ver_code": 1 - }, - { - "id": "schedule", - "ver_code": 2 - }, - { - "id": "countdown", - "ver_code": 2 - }, - { - "id": "antitheft", - "ver_code": 1 - }, - { - "id": "account", - "ver_code": 1 - }, - { - "id": "synchronize", - "ver_code": 1 - }, - { - "id": "sunrise_sunset", - "ver_code": 1 - }, - { - "id": "brightness", - "ver_code": 1 - }, - { - "id": "cloud_connect", - "ver_code": 1 - }, - { - "id": "color_temperature", - "ver_code": 1 - }, - { - "id": "default_states", - "ver_code": 1 - }, - { - "id": "preset", - "ver_code": 3 - }, - { - "id": "color", - "ver_code": 1 - }, - { - "id": "on_off_gradually", - "ver_code": 1 - }, - { - "id": "device_local_time", - "ver_code": 1 - }, - { - "id": "iot_cloud", - "ver_code": 1 - }, - { - "id": "music_rhythm", - "ver_code": 3 - }, - { - "id": "bulb_quick_control", - "ver_code": 1 - }, - { - "id": "localSmart", - "ver_code": 1 - }, - { - "id": "homekit", - "ver_code": 2 - }, - { - "id": "segment", - "ver_code": 1 - }, - { - "id": "segment_effect", - "ver_code": 1 - }, - { - "id": "auto_light", - "ver_code": 1 - } - ] - }, - "get_device_info": { - "device_id": "80232F6DB6945EE8A77766AAE47921B6200F8953", - "fw_ver": "1.1.2 Build 231212 Rel.210005", - "hw_ver": "1.0", - "type": "SMART.TAPOBULB", - "model": "L930", - "mac": "34-60-F9-D7-82-BE", - "hw_id": "630F72CFA5B3DE1350011A0D04DE98B5", - "fw_id": "00000000000000000000000000000000", - "oem_id": "295750760B3E226612F759AF641BAB8A", - "color_temp_range": [2500, 6500], - "overheated": false, - "ip": "192.168.11.44", - "time_diff": 180, - "ssid": "LS0t", - "rssi": -42, - "signal_level": 3, - "lang": "en_US", - "avatar": "light_strip", - "region": "", - "specs": "", - "nickname": "U21hcnQgTGlnaHQgU3RyaXA=", - "has_set_location_info": false, - "lighting_effect": { - "enable": 0, - "id": "TapoStrip_7UcYLeJbiaxVIXCxr21tpx", - "name": "Icicle", - "custom": 0, - "brightness": 100, - "display_colors": [[190, 100, 100]] - }, - "music_rhythm_enable": false, - "music_rhythm_mode": "single_lamp", - "segment_effect": { - "id": "TapoStrip_5lGMt9hvNCTEuquuCrX03B", - "name": "energetic", - "custom": 0, - "enable": 1, - "brightness": 100, - "display_colors": [[240, 100, 100, 0]] - }, - "device_on": true, - "brightness": 100, - "hue": 139, - "saturation": 78, - "color_temp": 6493, - "default_states": { - "type": "last_states", - "state": { - "segment_effect": { - "id": "TapoStrip_5lGMt9hvNCTEuquuCrX03B", - "name": "energetic", - "enable": 1, - "brightness": 100, - "custom": 0, - "display_colors": [[240, 100, 100, 0]] - } - } - } - } -} diff --git a/tests/fixtures/plug.json b/tests/fixtures/plug.json deleted file mode 100644 index ec70e5a..0000000 --- a/tests/fixtures/plug.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "get_device_info": { - "device_id": "80225A84E5F52C914E409EF8CCE7D68D20FAA0B9", - "fw_ver": "1.2.1 Build 230804 Rel.190922", - "hw_ver": "1.0", - "type": "SMART.TAPOPLUG", - "model": "P105", - "mac": "9C-53-22-A7-C8-35", - "hw_id": "96DE01611683F4A1D0B8B47C16A906EB", - "fw_id": "00000000000000000000000000000000", - "oem_id": "BE68AD1E42F876124BDE439023B3C796", - "ip": "192.168.1.40", - "time_diff": 60, - "ssid": "SG9tZS1OZXR3b3Jr", - "rssi": -47, - "signal_level": 3, - "auto_off_status": "on", - "auto_off_remain_time": 0, - "latitude": 437188, - "longitude": 128663, - "lang": "it_IT", - "avatar": "plug", - "region": "Europe/Rome", - "specs": "IT", - "nickname": "QWxiZXJvIE5hdGFsZQ==", - "has_set_location_info": true, - "device_on": true, - "on_time": 0, - "default_states": { - "type": "last_states", - "state": {} - }, - "overheated": false - }, - "component_nego": { - "component_list": [ - { "id": "device", "ver_code": 2 }, - { "id": "firmware", "ver_code": 2 }, - { "id": "quick_setup", "ver_code": 2 }, - { "id": "time", "ver_code": 2 }, - { "id": "wireless", "ver_code": 2 }, - { "id": "schedule", "ver_code": 2 }, - { "id": "countdown", "ver_code": 2 }, - { "id": "antitheft", "ver_code": 2 }, - { "id": "account", "ver_code": 2 }, - { "id": "synchronize", "ver_code": 2 }, - { "id": "sunrise_sunset", "ver_code": 2 }, - { "id": "led", "ver_code": 2 }, - { "id": "cloud_connect", "ver_code": 2 }, - { "id": "iot_cloud", "ver_code": 2 }, - { "id": "device_local_time", "ver_code": 2 }, - { "id": "default_states", "ver_code": 2 }, - { "id": "auto_off", "ver_code": 2 }, - { "id": "localSmart", "ver_code": 2 } - ] - } -} diff --git a/tests/fixtures/plug_emeter.json b/tests/fixtures/plug_emeter.json deleted file mode 100644 index 9a1d670..0000000 --- a/tests/fixtures/plug_emeter.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "get_device_info": { - "device_id": "80225A84E5F52C914E409EF8CCE7D68D20FAA0B4", - "fw_ver": "1.0.4 Build 230825 Rel.115051", - "hw_ver": "1.0", - "type": "SMART.TAPOPLUG", - "model": "P110M", - "mac": "I_WOULD_RATHER_NOT", - "hw_id": "I_WOULD_RATHER_NOT", - "fw_id": "00000000000000000000000000000000", - "oem_id": "F031I_WOULD_RATHER_NOT91C", - "ip": "192.168.0.8", - "time_diff": 0, - "ssid": "cGx1ZyBlbWV0ZXI=", - "rssi": -51, - "signal_level": 2, - "auto_off_status": "off", - "auto_off_remain_time": 0, - "latitude": 0, - "longitude": 0, - "lang": "", - "avatar": "", - "region": "Europe/Rome", - "specs": "", - "nickname": "cGx1ZyBlbWV0ZXI=", - "has_set_location_info": false, - "device_on": true, - "on_time": 2409, - "default_states": { - "type": "last_states", - "state": {} - }, - "overheated": false, - "power_protection_status": "normal", - "overcurrent_status": "normal" - }, - "component_nego": { - "component_list": [ - { - "id": "device", - "ver_code": 2 - }, - { - "id": "firmware", - "ver_code": 2 - }, - { - "id": "quick_setup", - "ver_code": 3 - }, - { - "id": "time", - "ver_code": 1 - }, - { - "id": "wireless", - "ver_code": 1 - }, - { - "id": "schedule", - "ver_code": 2 - }, - { - "id": "countdown", - "ver_code": 2 - }, - { - "id": "antitheft", - "ver_code": 1 - }, - { - "id": "account", - "ver_code": 1 - }, - { - "id": "synchronize", - "ver_code": 1 - }, - { - "id": "sunrise_sunset", - "ver_code": 1 - }, - { - "id": "led", - "ver_code": 1 - }, - { - "id": "cloud_connect", - "ver_code": 1 - }, - { - "id": "iot_cloud", - "ver_code": 1 - }, - { - "id": "device_local_time", - "ver_code": 1 - }, - { - "id": "default_states", - "ver_code": 1 - }, - { - "id": "auto_off", - "ver_code": 2 - }, - { - "id": "energy_monitoring", - "ver_code": 2 - }, - { - "id": "power_protection", - "ver_code": 1 - }, - { - "id": "matter", - "ver_code": 2 - }, - { - "id": "current_protection", - "ver_code": 1 - } - ] - }, - "get_current_power": { - "current_power": 1.2 - }, - "get_energy_usage": { - "current_power": 1.2, - "electricity_charge": [0, 0, 0], - "local_time": "2024-01-20 09:25:31", - "month_energy": 1421, - "month_runtime": 19742, - "today_energy": 0, - "today_runtime": 3 - } -} diff --git a/tests/fixtures/plug_strip.json b/tests/fixtures/plug_strip.json deleted file mode 100644 index 9f1cdb6..0000000 --- a/tests/fixtures/plug_strip.json +++ /dev/null @@ -1,290 +0,0 @@ -{ - "component_nego": { - "component_list": [ - { - "id": "device", - "ver_code": 2 - }, - { - "id": "firmware", - "ver_code": 2 - }, - { - "id": "quick_setup", - "ver_code": 3 - }, - { - "id": "time", - "ver_code": 1 - }, - { - "id": "wireless", - "ver_code": 1 - }, - { - "id": "schedule", - "ver_code": 2 - }, - { - "id": "countdown", - "ver_code": 2 - }, - { - "id": "antitheft", - "ver_code": 1 - }, - { - "id": "account", - "ver_code": 1 - }, - { - "id": "synchronize", - "ver_code": 1 - }, - { - "id": "sunrise_sunset", - "ver_code": 1 - }, - { - "id": "led", - "ver_code": 1 - }, - { - "id": "cloud_connect", - "ver_code": 1 - }, - { - "id": "iot_cloud", - "ver_code": 1 - }, - { - "id": "device_local_time", - "ver_code": 1 - }, - { - "id": "default_states", - "ver_code": 1 - }, - { - "id": "control_child", - "ver_code": 2 - }, - { - "id": "child_device", - "ver_code": 2 - }, - { - "id": "homekit", - "ver_code": 2 - }, - { - "id": "overheat_protection", - "ver_code": 1 - } - ] - }, - "get_device_info": { - "device_id": "802284163C98749A26DE2465A10AEDFC211D169C", - "fw_ver": "1.0.13 Build 230925 Rel.150200", - "hw_ver": "1.0", - "type": "SMART.TAPOPLUG", - "model": "P300", - "mac": "78-8C-B5-31-0A-35", - "hw_id": "553C7AE42A568240BEA222D090B68EF4", - "fw_id": "00000000000000000000000000000000", - "oem_id": "BCA35E01AEBC6BD91465057372DDAB74", - "ip": "192.168.1.7", - "time_diff": 60, - "ssid": "SG9tZS1OZXR3b3Jr", - "rssi": -73, - "signal_level": 1, - "longitude": 126667, - "latitude": 435773, - "lang": "en_US", - "avatar": "", - "region": "Europe/Rome", - "specs": "", - "nickname": "", - "has_set_location_info": true - }, - "get_child_device_list": { - "child_device_list": [ - { - "type": "SMART.TAPOPLUG", - "fw_ver": "1.0.13 Build 230925 Rel.150200", - "hw_ver": "1.0", - "model": "P300", - "mac": "788CB5310A35", - "hw_id": "553C7AE42A568240BEA222D090B68EF4", - "fw_id": "00000000000000000000000000000000", - "oem_id": "BCA35E01AEBC6BD91465057372DDAB74", - "category": "plug.powerstrip.sub-plug", - "status_follow_edge": true, - "device_id": "802284163C98749A26DE2465A10AEDFC211D169C02", - "original_device_id": "802284163C98749A26DE2465A10AEDFC211D169C", - "overheat_status": "normal", - "bind_count": 1, - "slot_number": 3, - "position": 3, - "on_time": 524990, - "nickname": "cGx1ZzM=", - "avatar": "plug", - "device_on": true, - "default_states": { - "type": "last_states" - }, - "region": "Europe/Rome", - "longitude": 126667, - "latitude": 435773, - "has_set_location_info": true - }, - { - "type": "SMART.TAPOPLUG", - "fw_ver": "1.0.13 Build 230925 Rel.150200", - "hw_ver": "1.0", - "model": "P300", - "mac": "788CB5310A35", - "hw_id": "553C7AE42A568240BEA222D090B68EF4", - "fw_id": "00000000000000000000000000000000", - "oem_id": "BCA35E01AEBC6BD91465057372DDAB74", - "category": "plug.powerstrip.sub-plug", - "status_follow_edge": true, - "device_id": "802284163C98749A26DE2465A10AEDFC211D169C01", - "original_device_id": "802284163C98749A26DE2465A10AEDFC211D169C", - "overheat_status": "normal", - "bind_count": 1, - "slot_number": 3, - "position": 2, - "on_time": 592773, - "nickname": "cGx1ZzI=s", - "avatar": "plug", - "device_on": true, - "default_states": { - "type": "last_states" - }, - "region": "Europe/Rome", - "longitude": 126667, - "latitude": 435773, - "has_set_location_info": true - }, - { - "type": "SMART.TAPOPLUG", - "fw_ver": "1.0.13 Build 230925 Rel.150200", - "hw_ver": "1.0", - "model": "P300", - "mac": "788CB5310A35", - "hw_id": "553C7AE42A568240BEA222D090B68EF4", - "fw_id": "00000000000000000000000000000000", - "oem_id": "BCA35E01AEBC6BD91465057372DDAB74", - "category": "plug.powerstrip.sub-plug", - "status_follow_edge": true, - "device_id": "802284163C98749A26DE2465A10AEDFC211D169C00", - "original_device_id": "802284163C98749A26DE2465A10AEDFC211D169C", - "overheat_status": "normal", - "bind_count": 1, - "slot_number": 3, - "position": 1, - "on_time": 524987, - "nickname": "cGx1ZzE=", - "avatar": "plug", - "device_on": true, - "default_states": { - "type": "last_states" - }, - "region": "Europe/Rome", - "longitude": 126667, - "latitude": 435773, - "has_set_location_info": true - } - ], - "start_index": 0, - "sum": 3 - }, - "get_device_info_802284163C98749A26DE2465A10AEDFC211D169C02": { - "type": "SMART.TAPOPLUG", - "fw_ver": "1.0.13 Build 230925 Rel.150200", - "hw_ver": "1.0", - "model": "P300", - "mac": "788CB5310A35", - "hw_id": "553C7AE42A568240BEA222D090B68EF4", - "fw_id": "00000000000000000000000000000000", - "oem_id": "BCA35E01AEBC6BD91465057372DDAB74", - "category": "plug.powerstrip.sub-plug", - "status_follow_edge": true, - "device_id": "802284163C98749A26DE2465A10AEDFC211D169C02", - "original_device_id": "802284163C98749A26DE2465A10AEDFC211D169C", - "overheat_status": "normal", - "bind_count": 1, - "slot_number": 3, - "position": 3, - "on_time": 524990, - "nickname": "cGx1ZzM=", - "avatar": "plug", - "device_on": true, - "default_states": { - "type": "last_states" - }, - "region": "Europe/Rome", - "longitude": 1, - "latitude": 1, - "has_set_location_info": true - }, - "get_device_info_802284163C98749A26DE2465A10AEDFC211D169C01": { - "type": "SMART.TAPOPLUG", - "fw_ver": "1.0.13 Build 230925 Rel.150200", - "hw_ver": "1.0", - "model": "P300", - "mac": "788CB5310A35", - "hw_id": "553C7AE42A568240BEA222D090B68EF4", - "fw_id": "00000000000000000000000000000000", - "oem_id": "BCA35E01AEBC6BD91465057372DDAB74", - "category": "plug.powerstrip.sub-plug", - "status_follow_edge": true, - "device_id": "802284163C98749A26DE2465A10AEDFC211D169C01", - "original_device_id": "802284163C98749A26DE2465A10AEDFC211D169C", - "overheat_status": "normal", - "bind_count": 1, - "slot_number": 3, - "position": 2, - "on_time": 592773, - "nickname": "cGx1ZzI=s", - "avatar": "plug", - "device_on": true, - "default_states": { - "type": "last_states" - }, - "region": "Europe/Rome", - "longitude": 1, - "latitude": 1, - "has_set_location_info": true - }, - "get_device_info_802284163C98749A26DE2465A10AEDFC211D169C00": { - "type": "SMART.TAPOPLUG", - "fw_ver": "1.0.13 Build 230925 Rel.150200", - "hw_ver": "1.0", - "model": "P300", - "mac": "788CB5310A35", - "hw_id": "553C7AE42A568240BEA222D090B68EF4", - "fw_id": "00000000000000000000000000000000", - "oem_id": "BCA35E01AEBC6BD91465057372DDAB74", - "category": "plug.powerstrip.sub-plug", - "status_follow_edge": true, - "device_id": "802284163C98749A26DE2465A10AEDFC211D169C00", - "original_device_id": "802284163C98749A26DE2465A10AEDFC211D169C", - "overheat_status": "normal", - "bind_count": 1, - "slot_number": 3, - "position": 1, - "on_time": 524987, - "nickname": "cGx1ZzE=", - "avatar": "plug", - "device_on": true, - "default_states": { - "type": "last_states" - }, - "region": "Europe/Rome", - "longitude": 1, - "latitude": 1, - "has_set_location_info": true - } -} diff --git a/tests/tapo_mock_helper.py b/tests/tapo_mock_helper.py deleted file mode 100644 index e52e791..0000000 --- a/tests/tapo_mock_helper.py +++ /dev/null @@ -1,34 +0,0 @@ -from plugp100.api.requests.tapo_request import TapoRequest -from plugp100.common.functional.tri import Try -from plugp100.responses.tapo_response import TapoResponse - - -class TapoResponseMockHelper: - def __init__(self, data: dict[str, Try[TapoResponse]]) -> None: - self.data = data - - async def get_response( - self, request: TapoRequest, child_id: str = "" - ) -> Try[TapoResponse]: - if request.method == "control_child": - return await self.get_response( - request.params.requestData.params.requests[0], - f"_{request.params.device_id}", - ) - - response = self.data.get( - f"{request.method}{child_id}", Try.of(TapoResponse(0, {}, None)) - ) - if child_id != "": - return tapo_response_child_of(response.get().result) - return response - - -def tapo_response_of(payload: dict[str, any]) -> Try[TapoResponse]: - return Try.of(TapoResponse(error_code=0, result=payload, msg="")) - - -def tapo_response_child_of(payload: dict[str, any]) -> Try[TapoResponse]: - return tapo_response_of( - {"responseData": {"result": {"responses": [{"result": payload}]}}} - ) diff --git a/tests/test_binary_sensor.py b/tests/test_binary_sensor.py index 7c5ed28..0dd24fe 100644 --- a/tests/test_binary_sensor.py +++ b/tests/test_binary_sensor.py @@ -18,7 +18,7 @@ @pytest.mark.parametrize( "device", - [mock_plug(), mock_hub(), mock_plug_strip(), mock_bulb(), mock_led_strip()], + [mock_plug(), mock_plug_strip(), mock_hub(), mock_bulb()] ) async def test_switch_overheat(hass: HomeAssistant, device: MagicMock): device_registry = dr.async_get(hass) @@ -29,7 +29,7 @@ async def test_switch_overheat(hass: HomeAssistant, device: MagicMock): state_entity = hass.states.get(entity_id) device = device_registry.async_get_device(identifiers={(DOMAIN, device.device_id)}) assert state_entity is not None - assert state_entity.state == "off" + assert state_entity.state == "on" assert state_entity.attributes["device_class"] == "heat" assert "overheat" in state_entity.attributes["friendly_name"].lower() assert device is not None diff --git a/tests/test_light.py b/tests/test_light.py index e3f365f..fa28b75 100644 --- a/tests/test_light.py +++ b/tests/test_light.py @@ -1,5 +1,5 @@ """Test tapo switch.""" -from unittest.mock import MagicMock +from unittest.mock import MagicMock, ANY import pytest from homeassistant.components.light import ATTR_BRIGHTNESS @@ -59,15 +59,19 @@ async def test_light_color_service_call(hass: HomeAssistant, tapo_device: MagicM await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}, blocking=True ) - tapo_device.off.assert_called_once() + tapo_device.turn_off.assert_called_once() await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100}, blocking=True, ) - tapo_device.on.assert_called_once() - tapo_device.set_brightness.assert_called_with(39) + tapo_device.turn_on.assert_called_once() + ## TODO: create only one set brightness method + if tapo_device.is_led_strip: + tapo_device.set_light_effect_brightness.assert_called_with(ANY, 39) + else: + tapo_device.set_brightness.assert_called_with(39) await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_ON, @@ -134,7 +138,7 @@ async def test_light_turn_on_with_attributes( {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 30, ATTR_HS_COLOR: (30, 10)}, blocking=True, ) - assert tapo_device.on.called + assert tapo_device.turn_on.called tapo_device.set_hue_saturation.assert_called_with(30, 10) tapo_device.set_brightness.assert_called_with(12) @@ -153,7 +157,7 @@ async def test_light_turn_on_with_effect(hass: HomeAssistant, tapo_device: Magic }, blocking=True, ) - assert tapo_device.on.called + assert tapo_device.turn_on.called tapo_device.set_light_effect.assert_called_with( LightEffectPreset.Christmas.to_effect() ) @@ -162,7 +166,7 @@ async def test_light_turn_on_with_effect(hass: HomeAssistant, tapo_device: Magic ) -@pytest.mark.parametrize("tapo_device", [mock_bulb(components_to_exclude=["color"])]) +@pytest.mark.parametrize("tapo_device", [mock_bulb(is_color=False)]) async def test_color_temp_only_light(hass: HomeAssistant, tapo_device: MagicMock): await setup_platform(hass, tapo_device, [LIGHT_DOMAIN]) entity_id = await extract_entity_id(tapo_device, LIGHT_DOMAIN) diff --git a/tests/test_sensor.py b/tests/test_sensor.py index 98dbd95..0a80a30 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -18,7 +18,7 @@ @pytest.mark.parametrize( "device", - [mock_plug(), mock_hub(), mock_plug_strip(), mock_bulb(), mock_led_strip()], + [mock_plug(), mock_plug_strip(), mock_hub(), mock_bulb() ] ) async def test_signal_sensor(hass: HomeAssistant, device: MagicMock): device_registry = dr.async_get(hass) diff --git a/tests/test_siren.py b/tests/test_siren.py index 400b26a..457917c 100644 --- a/tests/test_siren.py +++ b/tests/test_siren.py @@ -12,9 +12,9 @@ async def test_hub_siren_on(hass: HomeAssistant): device = mock_hub() await setup_platform(hass, device, PLATFORMS) - entity_id = "siren.smart_hub_siren" + entity_id = "siren.nickname_siren" state = hass.states.get(entity_id) - assert state.state == "off" + assert state.state == "on" await hass.services.async_call( SIREN_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}, blocking=True ) @@ -25,9 +25,9 @@ async def test_hub_siren_on(hass: HomeAssistant): async def test_hub_siren_off(hass: HomeAssistant): device = mock_hub() await setup_platform(hass, device, PLATFORMS) - entity_id = "siren.smart_hub_siren" + entity_id = "siren.nickname_siren" state = hass.states.get(entity_id) - assert state.state == "off" + assert state.state == "on" await hass.services.async_call( SIREN_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}, blocking=True ) diff --git a/tests/test_switch.py b/tests/test_switch.py index aeabc71..9508738 100644 --- a/tests/test_switch.py +++ b/tests/test_switch.py @@ -1,4 +1,6 @@ """Test tapo switch.""" +import logging + from custom_components.tapo.const import DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.switch import SERVICE_TURN_OFF @@ -20,10 +22,10 @@ async def test_switch_setup(hass: HomeAssistant): entity_id = await extract_entity_id(device, SWITCH_DOMAIN) state_entity = hass.states.get(entity_id) device = device_registry.async_get_device(identifiers={(DOMAIN, device.device_id)}) + assert device is not None assert state_entity is not None assert state_entity.state == "on" assert state_entity.attributes["device_class"] == "outlet" - assert device is not None async def test_switch_turn_on_service(hass: HomeAssistant): @@ -38,7 +40,7 @@ async def test_switch_turn_on_service(hass: HomeAssistant): {ATTR_ENTITY_ID: entity_id}, blocking=True, ) - device.on.assert_called_once() + device.turn_on.assert_called_once() async def test_switch_turn_off_service(hass: HomeAssistant): @@ -53,7 +55,7 @@ async def test_switch_turn_off_service(hass: HomeAssistant): {ATTR_ENTITY_ID: entity_id}, blocking=True, ) - device.off.assert_called_once() + device.turn_off.assert_called_once() async def test_plug_strip_child_onoff(hass: HomeAssistant): @@ -61,11 +63,12 @@ async def test_plug_strip_child_onoff(hass: HomeAssistant): await extract_entity_id(device, SWITCH_DOMAIN) assert await setup_platform(hass, device, [SWITCH_DOMAIN]) is not None expected_children_state = { - "switch.p300_plug1": {"value": "on"}, - "switch.p300_plug2": {"value": "on"}, - "switch.p300_plug3": {"value": "on"}, + "switch.nickname0": {"value": "on"}, + "switch.nickname1": {"value": "on"}, + "switch.nickname2": {"value": "on"}, } - for children_id, state in expected_children_state.items(): + sockets = device.sockets + for (sock, (children_id, state)) in zip(sockets, expected_children_state.items()): assert hass.states.get(children_id).state == state["value"] await hass.services.async_call( SWITCH_DOMAIN, @@ -73,13 +76,13 @@ async def test_plug_strip_child_onoff(hass: HomeAssistant): {ATTR_ENTITY_ID: children_id}, blocking=True, ) - device.on.assert_called_once() - device.on.reset_mock() + sock.turn_on.assert_called_once() + sock.turn_on.reset_mock() await hass.services.async_call( SWITCH_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: children_id}, blocking=True, ) - device.off.assert_called_once() - device.off.reset_mock() + sock.turn_off.assert_called_once() + sock.turn_off.reset_mock() diff --git a/tests/unit/hub/conftest.py b/tests/unit/hub/conftest.py new file mode 100644 index 0000000..5f76d84 --- /dev/null +++ b/tests/unit/hub/conftest.py @@ -0,0 +1,7 @@ +import pytest +from homeassistant.core import HomeAssistant + + +@pytest.fixture +def enable_custom_integrations(hass: HomeAssistant) -> bool: + return False \ No newline at end of file diff --git a/tests/unit/hub/test_binary_sensor.py b/tests/unit/hub/test_binary_sensor.py index 16930c7..ea60be9 100644 --- a/tests/unit/hub/test_binary_sensor.py +++ b/tests/unit/hub/test_binary_sensor.py @@ -1,27 +1,18 @@ -from custom_components.tapo.hub.binary_sensor import LowBatterySensor -from custom_components.tapo.hub.binary_sensor import MotionSensor -from custom_components.tapo.hub.binary_sensor import SENSOR_MAPPING -from custom_components.tapo.hub.binary_sensor import SmartDoorSensor -from custom_components.tapo.hub.binary_sensor import WaterLeakSensor -from plugp100.api.hub.ke100_device import KE100Device -from plugp100.api.hub.s200b_device import S200ButtonDevice -from plugp100.api.hub.switch_child_device import SwitchChildDevice -from plugp100.api.hub.t100_device import T100MotionSensor -from plugp100.api.hub.t110_device import T110SmartDoor -from plugp100.api.hub.t31x_device import T31Device -from plugp100.api.hub.water_leak_device import WaterLeakSensor as WaterLeakDevice +from plugp100.new.components.battery_component import BatteryComponent +from plugp100.new.components.motion_sensor_component import MotionSensorComponent +from plugp100.new.components.smart_door_component import SmartDoorComponent +from plugp100.new.components.water_leak_component import WaterLeakComponent + +from custom_components.tapo.hub.binary_sensor import COMPONENT_MAPPING class TestSensorMappings: def test_binary_sensor_mappings(self): expected_mappings = { - T31Device: [LowBatterySensor], - T110SmartDoor: [SmartDoorSensor, LowBatterySensor], - S200ButtonDevice: [LowBatterySensor], - T100MotionSensor: [MotionSensor, LowBatterySensor], - SwitchChildDevice: [LowBatterySensor], - WaterLeakDevice: [WaterLeakSensor, LowBatterySensor], - KE100Device: [LowBatterySensor], + SmartDoorComponent: 'SmartDoorSensor', + WaterLeakComponent: 'WaterLeakSensor', + MotionSensorComponent: 'MotionSensor', + BatteryComponent: 'LowBatterySensor' } - assert SENSOR_MAPPING == expected_mappings + assert COMPONENT_MAPPING == expected_mappings diff --git a/tests/unit/hub/test_climate.py b/tests/unit/hub/test_climate.py index c1f2e64..7903a30 100644 --- a/tests/unit/hub/test_climate.py +++ b/tests/unit/hub/test_climate.py @@ -1,98 +1,63 @@ -from unittest.mock import AsyncMock +from unittest.mock import AsyncMock, MagicMock from unittest.mock import Mock import pytest +from plugp100.common.functional.tri import Try +from plugp100.new.child.tapohubchildren import KE100Device + from custom_components.tapo.coordinators import TapoDataCoordinator -from custom_components.tapo.hub.climate import SENSOR_MAPPING from custom_components.tapo.hub.climate import TRVClimate from homeassistant.components.climate import ClimateEntityFeature from homeassistant.components.climate import HVACMode from homeassistant.const import UnitOfTemperature -from plugp100.api.hub.ke100_device import KE100Device from plugp100.responses.hub_childs.ke100_device_state import TRVState from plugp100.responses.temperature_unit import TemperatureUnit +from tests.conftest import _mock_hub_child_device -class TestSensorMappings: - coordinator = Mock(TapoDataCoordinator) - - def test_binary_sensor_mappings(self): - expected_mappings = {KE100Device: [TRVClimate]} - assert SENSOR_MAPPING == expected_mappings +# class TestSensorMappings: +# coordinator = Mock(TapoDataCoordinator) +# +# def test_binary_sensor_mappings(self): +# expected_mappings = {KE100Device: [TRVClimate]} +# +# assert SENSOR_MAPPING == expected_mappings class TestTRVClimate: - coordinator = Mock(TapoDataCoordinator) - def test_unique_id(self): - base_data = Mock() - base_data.base_info.device_id = "hub1234" - self.coordinator.get_state_of.return_value = base_data + @pytest.fixture(autouse=True) + def init_data(self): + self.coordinator = Mock(TapoDataCoordinator) + self.device = _mock_hub_child_device(MagicMock(auto_spec=KE100Device)) + self.climate = TRVClimate(coordinator=self.coordinator, device=self.device) - subject = TRVClimate(coordinator=self.coordinator) - result = subject.unique_id - - assert result == "hub1234_Climate" + def test_unique_id(self): + assert self.climate.unique_id == "123_Climate" def test_supported_features(self): - subject = TRVClimate(coordinator=self.coordinator) - - result = subject.supported_features - - assert result == ClimateEntityFeature.TARGET_TEMPERATURE + assert self.climate.supported_features == ClimateEntityFeature.TARGET_TEMPERATURE def test_hvac_modes(self): - subject = TRVClimate(coordinator=self.coordinator) - - result = subject.hvac_modes - - assert result == [HVACMode.OFF, HVACMode.HEAT] + assert self.climate.hvac_modes == [HVACMode.OFF, HVACMode.HEAT] def test_min_temp(self): - base_data = Mock() - base_data.min_control_temperature = 5.0 - self.coordinator.get_state_of.return_value = base_data - - subject = TRVClimate(coordinator=self.coordinator) - - result = subject.min_temp - - assert result == 5.0 + self.device.range_control_temperature = [5.0, 30.0] + assert self.climate.min_temp == 5.0 def test_max_temp(self): - base_data = Mock() - base_data.max_control_temperature = 30.0 - self.coordinator.get_state_of.return_value = base_data - - subject = TRVClimate(coordinator=self.coordinator) - - result = subject.max_temp - - assert result == 30.0 + self.device.range_control_temperature = [5.0, 30.0] + assert self.climate.max_temp == 30.0 def test_current_temperature(self): - base_data = Mock() - base_data.current_temperature = 20.1 - self.coordinator.get_state_of.return_value = base_data - - subject = TRVClimate(coordinator=self.coordinator) - - result = subject.current_temperature - - assert result == 20.1 + self.device.temperature = 20.1 + assert self.climate.current_temperature == 20.1 def test_target_temperature(self): - base_data = Mock() - base_data.target_temperature = 22.0 - self.coordinator.get_state_of.return_value = base_data - - subject = TRVClimate(coordinator=self.coordinator) - - result = subject.target_temperature - - assert result == 22.0 + self.device.target_temperature = 22.0 + assert self.climate.target_temperature == 22.0 @pytest.mark.parametrize( "trv_temperature_unit, expected_unit_of_temperature", @@ -106,67 +71,38 @@ def test_temperature_unit( trv_temperature_unit: TemperatureUnit, expected_unit_of_temperature: UnitOfTemperature, ): - base_data = Mock() - base_data.temperature_unit = trv_temperature_unit - self.coordinator.get_state_of.return_value = base_data - - subject = TRVClimate(coordinator=self.coordinator) - - result = subject.temperature_unit - - assert result == expected_unit_of_temperature + self.device.temperature_unit = trv_temperature_unit + assert self.climate.temperature_unit == expected_unit_of_temperature @pytest.mark.parametrize( "trv_state, expected_hvac_mode", [(TRVState.HEATING, HVACMode.HEAT), (TRVState.OFF, HVACMode.OFF)], ) def test_hvac_mode(self, trv_state: TRVState, expected_hvac_mode: HVACMode): - base_data = Mock() - base_data.trv_state = trv_state - self.coordinator.get_state_of.return_value = base_data - - subject = TRVClimate(coordinator=self.coordinator) - - result = subject.hvac_mode - - assert result == expected_hvac_mode + self.device.state = trv_state + assert self.climate.hvac_mode == expected_hvac_mode @pytest.mark.asyncio async def test_async_hvac_mode_heat(self): - async_coordinator = AsyncMock(TapoDataCoordinator) - device = AsyncMock() - async_coordinator.device = device - - subject = TRVClimate(coordinator=async_coordinator) + self.device.set_frost_protection_off = AsyncMock(return_value=Try.of(True)) + self.device.set_frost_protection_on = AsyncMock(return_value=Try.of(True)) + await self.climate.async_set_hvac_mode(HVACMode.HEAT) - await subject.async_set_hvac_mode(HVACMode.HEAT) - - device.set_frost_protection_on.set_frost_protection_off() - async_coordinator.async_request_refresh.assert_called_once() + self.device.set_frost_protection_on.assert_not_called() + self.device.set_frost_protection_off.assert_called_once() @pytest.mark.asyncio async def test_async_hvac_mode_off(self): - async_coordinator = AsyncMock(TapoDataCoordinator) - device = AsyncMock() - async_coordinator.device = device - - subject = TRVClimate(coordinator=async_coordinator) + self.device.set_frost_protection_off = AsyncMock(return_value=Try.of(True)) + self.device.set_frost_protection_on = AsyncMock(return_value=Try.of(True)) + await self.climate.async_set_hvac_mode(HVACMode.OFF) - await subject.async_set_hvac_mode(HVACMode.OFF) - - device.set_frost_protection_on.set_frost_protection_on() - async_coordinator.async_request_refresh.assert_called_once() + self.device.set_frost_protection_off.assert_not_called() + self.device.set_frost_protection_on.assert_called_once() @pytest.mark.asyncio async def test_async_set_temperature(self): temp_args = {"temperature": 18} - async_coordinator = AsyncMock(TapoDataCoordinator) - device = AsyncMock() - async_coordinator.device = device - - subject = TRVClimate(coordinator=async_coordinator) - - await subject.async_set_temperature(**temp_args) - - device.set_target_temp.assert_called_once_with(temp_args) - async_coordinator.async_request_refresh.assert_called_once() + self.device.set_target_temp = AsyncMock(return_value=Try.of(True)) + await self.climate.async_set_temperature(**temp_args) + self.device.set_target_temp.assert_called_once_with(temp_args) diff --git a/tests/unit/hub/test_number.py b/tests/unit/hub/test_number.py index 276dc30..120346b 100644 --- a/tests/unit/hub/test_number.py +++ b/tests/unit/hub/test_number.py @@ -1,85 +1,57 @@ -from unittest.mock import AsyncMock +from unittest.mock import MagicMock, AsyncMock from unittest.mock import Mock import pytest -from custom_components.tapo.coordinators import TapoDataCoordinator -from custom_components.tapo.hub.number import SENSOR_MAPPING -from custom_components.tapo.hub.number import TRVTemperatureOffset from homeassistant.components.number import NumberDeviceClass from homeassistant.components.number import NumberMode from homeassistant.const import UnitOfTemperature -from plugp100.api.hub.ke100_device import KE100Device +from plugp100.common.functional.tri import Try +from plugp100.new.child.tapohubchildren import KE100Device from plugp100.responses.temperature_unit import TemperatureUnit +from custom_components.tapo.coordinators import TapoDataCoordinator +from custom_components.tapo.hub.number import TRVTemperatureOffset +from tests.conftest import _mock_hub_child_device -class TestSensorMappings: - coordinator = Mock(TapoDataCoordinator) - - def test_binary_sensor_mappings(self): - expected_mappings = {KE100Device: [TRVTemperatureOffset]} - assert SENSOR_MAPPING == expected_mappings +# class TestSensorMappings: +# coordinator = Mock(TapoDataCoordinator) +# +# def test_binary_sensor_mappings(self): +# expected_mappings = {KE100Device: [TRVTemperatureOffset]} +# +# assert SENSOR_MAPPING == expected_mappings class TestTRVTemperatureOffset: - coordinator = Mock(TapoDataCoordinator) - - def test_unique_id(self): - base_data = Mock() - base_data.base_info.device_id = "hub1234" - self.coordinator.get_state_of.return_value = base_data - - subject = TRVTemperatureOffset(coordinator=self.coordinator) - - result = subject.unique_id - - assert result == "hub1234_Temperature_Offset" - - def test_device_class(self): - subject = TRVTemperatureOffset(coordinator=self.coordinator) - - result = subject.device_class - - assert result == NumberDeviceClass.TEMPERATURE - - def test_native_min_value(self): - subject = TRVTemperatureOffset(coordinator=self.coordinator) - result = subject.native_min_value + @pytest.fixture(autouse=True) + def init_data(self): + self.coordinator = Mock(TapoDataCoordinator) + self.device = _mock_hub_child_device(MagicMock(auto_spec=KE100Device)) + self.temperature_offset = TRVTemperatureOffset(coordinator=self.coordinator, device=self.device) - assert result == -10 + async def test_unique_id(self): + assert self.temperature_offset.unique_id == "123_Temperature_Offset" - def test_native_max_value(self): - subject = TRVTemperatureOffset(coordinator=self.coordinator) + async def test_device_class(self): + assert self.temperature_offset.device_class == NumberDeviceClass.TEMPERATURE - result = subject.native_max_value + async def test_native_min_value(self): + assert self.temperature_offset.native_min_value == -10 - assert result == 10 + async def test_native_max_value(self): + assert self.temperature_offset.native_max_value == 10 - def test_mode(self): - subject = TRVTemperatureOffset(coordinator=self.coordinator) + async def test_mode(self): + assert self.temperature_offset.mode == NumberMode.AUTO - result = subject.mode + async def test_native_step(self): + assert self.temperature_offset.native_step == 1 - assert result == NumberMode.AUTO - - def test_native_step(self): - subject = TRVTemperatureOffset(coordinator=self.coordinator) - - result = subject.native_step - - assert result == 1 - - def test_native_value(self): - base_data = Mock() - base_data.temperature_offset = -4 - self.coordinator.get_state_of.return_value = base_data - - subject = TRVTemperatureOffset(coordinator=self.coordinator) - - result = subject.native_value - - assert result == -4 + async def test_native_value(self): + self.device.temperature_offset = -4 + assert self.temperature_offset.native_value == -4 @pytest.mark.parametrize( "temperature_unit, expected_unit_of_temperature", @@ -88,31 +60,17 @@ def test_native_value(self): (TemperatureUnit.FAHRENHEIT, UnitOfTemperature.FAHRENHEIT), ], ) - def test_native_unit_of_measurement( + async def test_native_unit_of_measurement( self, temperature_unit: TemperatureUnit, expected_unit_of_temperature: UnitOfTemperature, ): - base_data = Mock() - base_data.temperature_unit = temperature_unit - self.coordinator.get_state_of.return_value = base_data - - subject = TRVTemperatureOffset(coordinator=self.coordinator) - - result = subject.native_unit_of_measurement + self.device.temperature_unit = temperature_unit + assert self.temperature_offset.native_unit_of_measurement == expected_unit_of_temperature - assert result == expected_unit_of_temperature - @pytest.mark.asyncio async def test_async_set_native_value(self): value = 8 - async_coordinator = AsyncMock(TapoDataCoordinator) - device = AsyncMock() - async_coordinator.device = device - - subject = TRVTemperatureOffset(coordinator=async_coordinator) - - await subject.async_set_native_value(value=value) - - device.set_temp_offset.assert_called_once_with(value) - async_coordinator.async_request_refresh.assert_called_once() + self.device.set_temp_offset = AsyncMock(return_value=Try.of(True)) + await self.temperature_offset.async_set_native_value(value=value) + self.device.set_temp_offset.assert_called_once_with(value) diff --git a/tests/unit/hub/test_sensor.py b/tests/unit/hub/test_sensor.py index 977b374..5861a07 100644 --- a/tests/unit/hub/test_sensor.py +++ b/tests/unit/hub/test_sensor.py @@ -1,80 +1,60 @@ -from unittest.mock import Mock +from unittest.mock import Mock, MagicMock, AsyncMock -from custom_components.tapo.coordinators import TapoDataCoordinator -from custom_components.tapo.hub.sensor import BatteryLevelSensor -from custom_components.tapo.hub.sensor import HumiditySensor -from custom_components.tapo.hub.sensor import ReportIntervalDiagnostic -from custom_components.tapo.hub.sensor import SENSOR_MAPPING -from custom_components.tapo.hub.sensor import TemperatureSensor +import pytest from homeassistant.components.sensor import SensorDeviceClass from homeassistant.components.sensor import SensorStateClass from homeassistant.const import PERCENTAGE -from plugp100.api.hub.ke100_device import KE100Device -from plugp100.api.hub.s200b_device import S200ButtonDevice -from plugp100.api.hub.t100_device import T100MotionSensor -from plugp100.api.hub.t110_device import T110SmartDoor -from plugp100.api.hub.t31x_device import T31Device -from plugp100.api.hub.water_leak_device import WaterLeakSensor as WaterLeakDevice +from plugp100.new.child.tapohubchildren import TapoHubChildDevice +from plugp100.new.components.battery_component import BatteryComponent +from plugp100.new.components.humidity_component import HumidityComponent +from plugp100.new.components.report_mode_component import ReportModeComponent +from plugp100.new.components.temperature_component import TemperatureComponent + +from custom_components.tapo.coordinators import TapoDataCoordinator +from custom_components.tapo.hub.sensor import BatteryLevelSensor, COMPONENT_MAPPING +from tests.conftest import _mock_hub_child_device class TestSensorMappings: coordinator = Mock(TapoDataCoordinator) - def test_binary_sensor_mappings(self): + def test_sensor_mappings(self): + expected_mappings = { - T31Device: [HumiditySensor, TemperatureSensor, ReportIntervalDiagnostic], - T110SmartDoor: [ReportIntervalDiagnostic], - S200ButtonDevice: [ReportIntervalDiagnostic], - T100MotionSensor: [ReportIntervalDiagnostic], - WaterLeakDevice: [ReportIntervalDiagnostic], - KE100Device: [TemperatureSensor, BatteryLevelSensor], + HumidityComponent: 'HumiditySensor', + TemperatureComponent: 'TemperatureSensor', + ReportModeComponent: 'ReportIntervalDiagnostic', + BatteryComponent: 'BatteryLevelSensor' } - assert SENSOR_MAPPING == expected_mappings + assert COMPONENT_MAPPING == expected_mappings +def _mock_battery_component(mock_device: MagicMock) -> MagicMock: + battery_component = MagicMock(BatteryComponent()) + battery_component.battery_percentage = 100 + battery_component.is_battery_low = False + battery_component.update = AsyncMock(return_value=None) + mock_device.add_component(battery_component) + return mock_device class TestBatteryLevelSensor: - coordinator = Mock(TapoDataCoordinator) + @pytest.fixture(autouse=True) + def init_data(self): + self.coordinator = Mock(TapoDataCoordinator) + self.device = _mock_battery_component(_mock_hub_child_device(MagicMock(auto_spec=TapoHubChildDevice))) + self.battery_sensor = BatteryLevelSensor(coordinator=self.coordinator, device=self.device) def test_unique_id(self): - base_data = Mock() - base_data.base_info.device_id = "hub1234" - self.coordinator.get_state_of.return_value = base_data - - subject = BatteryLevelSensor(coordinator=self.coordinator) - - result = subject.unique_id - - assert result == "hub1234_Battery_Percentage" + assert self.battery_sensor.unique_id == "123_Battery_Percentage" def test_device_class(self): - subject = BatteryLevelSensor(coordinator=self.coordinator) - - result = subject.device_class - - assert result == SensorDeviceClass.BATTERY + assert self.battery_sensor.device_class == SensorDeviceClass.BATTERY def test_state_class(self): - subject = BatteryLevelSensor(coordinator=self.coordinator) - - result = subject.state_class - - assert result == SensorStateClass.MEASUREMENT + assert self.battery_sensor.state_class == SensorStateClass.MEASUREMENT def test_native_unit_of_measurement(self): - subject = BatteryLevelSensor(coordinator=self.coordinator) - - result = subject.native_unit_of_measurement - - assert result == PERCENTAGE + assert self.battery_sensor.native_unit_of_measurement == PERCENTAGE def test_native_value(self): - base_data = Mock() - base_data.battery_percentage = 20 - self.coordinator.get_state_of.return_value = base_data - - subject = BatteryLevelSensor(coordinator=self.coordinator) - - result = subject.native_value - - assert result == 20 + assert self.battery_sensor.native_value == 100 diff --git a/tests/unit/hub/test_switch.py b/tests/unit/hub/test_switch.py index 9012348..4f7310c 100644 --- a/tests/unit/hub/test_switch.py +++ b/tests/unit/hub/test_switch.py @@ -1,142 +1,81 @@ -from unittest.mock import AsyncMock +from unittest.mock import AsyncMock, MagicMock from unittest.mock import Mock import pytest +from homeassistant.components.switch import SwitchDeviceClass +from plugp100.common.functional.tri import Try +from plugp100.new.child.tapohubchildren import KE100Device + from custom_components.tapo.coordinators import TapoDataCoordinator -from custom_components.tapo.hub.switch import SWITCH_MAPPING -from custom_components.tapo.hub.switch import SwitchTapoChild from custom_components.tapo.hub.switch import TRVChildLock from custom_components.tapo.hub.switch import TRVFrostProtection -from homeassistant.components.switch import SwitchDeviceClass -from plugp100.api.hub.ke100_device import KE100Device -from plugp100.api.hub.switch_child_device import SwitchChildDevice - - -class TestSensorMappings: - coordinator = Mock(TapoDataCoordinator) +from tests.conftest import _mock_hub_child_device - def test_binary_sensor_mappings(self): - expected_mappings = { - SwitchChildDevice: [SwitchTapoChild], - KE100Device: [TRVFrostProtection, TRVChildLock], - } - assert SWITCH_MAPPING == expected_mappings +# TODO: convert to try setup config entry +# class TestSensorMappings: +# coordinator = Mock(TapoDataCoordinator) +# +# def test_binary_sensor_mappings(self): +# expected_mappings = { +# SwitchChildDevice: [SwitchTapoChild], +# KE100Device: [TRVFrostProtection, TRVChildLock], +# } +# +# assert SWITCH_MAPPING == expected_mappings class TestTRVFrostProtection: - coordinator = Mock(TapoDataCoordinator) - def test_unique_id(self): - base_data = Mock() - base_data.base_info.device_id = "hub1234" - self.coordinator.get_state_of.return_value = base_data + @pytest.fixture(autouse=True) + def init_data(self): + self.coordinator = Mock(TapoDataCoordinator) + self.device = _mock_hub_child_device(MagicMock(auto_spec=KE100Device)) + self.frost_protection = TRVFrostProtection(coordinator=self.coordinator, device=self.device) - subject = TRVFrostProtection(coordinator=self.coordinator) - - result = subject.unique_id - - assert result == "hub1234_Frost_Protection" - - def test_is_on(self): - base_data = Mock() - base_data.frost_protection_on = False - self.coordinator.get_state_of.return_value = base_data + async def test_unique_id(self): + assert self.frost_protection.unique_id == "123_Frost_Protection" - subject = TRVFrostProtection(coordinator=self.coordinator) + async def test_is_on(self): + assert self.frost_protection.is_on is False - result = subject.is_on + async def test_device_class(self): + assert self.frost_protection.device_class == SwitchDeviceClass.SWITCH - assert result is False - - def test_device_class(self): - subject = TRVFrostProtection(coordinator=self.coordinator) - - result = subject.device_class - - assert result == SwitchDeviceClass.SWITCH - - @pytest.mark.asyncio async def test_async_turn_on(self): - async_coordinator = AsyncMock(TapoDataCoordinator) - device = AsyncMock() - async_coordinator.device = device - - subject = TRVFrostProtection(coordinator=async_coordinator) - - await subject.async_turn_on() - - device.set_frost_protection_on.assert_called_once() - async_coordinator.async_request_refresh.assert_called_once() + self.device.set_frost_protection_on = AsyncMock(return_value=Try.of(True)) + await self.frost_protection.async_turn_on() + self.device.set_frost_protection_on.assert_called_once() - @pytest.mark.asyncio async def test_async_turn_off(self): - async_coordinator = AsyncMock(TapoDataCoordinator) - device = AsyncMock() - async_coordinator.device = device - - subject = TRVFrostProtection(coordinator=async_coordinator) - - await subject.async_turn_off() - - device.set_frost_protection_off.assert_called_once() - async_coordinator.async_request_refresh.assert_called_once() + self.device.set_frost_protection_off = AsyncMock(return_value=Try.of(True)) + await self.frost_protection.async_turn_off() + self.device.set_frost_protection_off.assert_called_once() class TestTRVChildLock: - coordinator = Mock(TapoDataCoordinator) - - def test_unique_id(self): - base_data = Mock() - base_data.base_info.device_id = "hub1234" - self.coordinator.get_state_of.return_value = base_data - - subject = TRVChildLock(coordinator=self.coordinator) + @pytest.fixture(autouse=True) + def init_data(self): + self.coordinator = Mock(TapoDataCoordinator) + self.device = _mock_hub_child_device(MagicMock(auto_spec=KE100Device)) + self.child_lock = TRVChildLock(coordinator=self.coordinator, device=self.device) - result = subject.unique_id - - assert result == "hub1234_Child_Lock" + async def test_unique_id(self): + assert self.child_lock.unique_id == "123_Child_Lock" def test_is_on(self): - base_data = Mock() - base_data.child_protection = True - self.coordinator.get_state_of.return_value = base_data - - subject = TRVChildLock(coordinator=self.coordinator) - - result = subject.is_on - - assert result is True + self.device.is_child_protection_on = True + assert self.child_lock.is_on is True def test_device_class(self): - subject = TRVChildLock(coordinator=self.coordinator) + assert self.child_lock.device_class == SwitchDeviceClass.SWITCH - result = subject.device_class - - assert result == SwitchDeviceClass.SWITCH - - @pytest.mark.asyncio async def test_async_turn_on(self): - async_coordinator = AsyncMock(TapoDataCoordinator) - device = AsyncMock() - async_coordinator.device = device - - subject = TRVChildLock(coordinator=async_coordinator) - - await subject.async_turn_on() + self.device.set_child_protection_on = AsyncMock(return_value=Try.of(True)) + await self.child_lock.async_turn_on() + self.device.set_child_protection_on.assert_called_once() - device.set_child_protection_on.assert_called_once() - async_coordinator.async_request_refresh.assert_called_once() - - @pytest.mark.asyncio async def test_async_turn_off(self): - async_coordinator = AsyncMock(TapoDataCoordinator) - device = AsyncMock() - async_coordinator.device = device - - subject = TRVChildLock(coordinator=async_coordinator) - - await subject.async_turn_off() - - device.set_child_protection_off.assert_called_once() - async_coordinator.async_request_refresh.assert_called_once() + self.device.set_child_protection_off = AsyncMock(return_value=Try.of(True)) + await self.child_lock.async_turn_off() + self.device.set_child_protection_off.assert_called_once() diff --git a/tests/unit/hub/test_tapo_hub.py b/tests/unit/hub/test_tapo_hub.py index 6b4acb4..5a6b1aa 100644 --- a/tests/unit/hub/test_tapo_hub.py +++ b/tests/unit/hub/test_tapo_hub.py @@ -1,100 +1,100 @@ -from datetime import timedelta -from unittest.mock import Mock -from unittest.mock import patch - -import pytest -from custom_components.tapo.hub.hass_tapo_hub import TapoHub -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant -from homeassistant.helpers.device_registry import DeviceRegistry -from plugp100.api.hub.hub_device import HubDevice -from plugp100.api.hub.ke100_device import KE100Device -from plugp100.api.hub.s200b_device import S200ButtonDevice -from plugp100.api.hub.switch_child_device import SwitchChildDevice -from plugp100.api.hub.t100_device import T100MotionSensor -from plugp100.api.hub.t110_device import T110SmartDoor -from plugp100.api.hub.t31x_device import T31Device -from plugp100.responses.hub_childs.hub_child_base_info import HubChildBaseInfo - - -class TestTapoHub: - config = Mock(ConfigEntry) - hub_device = Mock(HubDevice) - hass = Mock(HomeAssistant) - registry = Mock(DeviceRegistry) - polling_rate = Mock(timedelta) - - @pytest.mark.asyncio - @pytest.mark.parametrize( - "model, expected_type", - [ - ("KE100", KE100Device), - ("T31", T31Device), - ("T110", T110SmartDoor), - ("S200", S200ButtonDevice), - ("T100", T100MotionSensor), - ("S220", SwitchChildDevice), - ("S210", SwitchChildDevice), - ], - ) - async def test_setup_child_coordinators_should_create_correct_types( - self, model: str, expected_type: type - ): - with patch( - "homeassistant.helpers.update_coordinator.DataUpdateCoordinator.async_config_entry_first_refresh" - ): - with patch( - "homeassistant.helpers.device_registry.DeviceRegistry.async_get_or_create" - ): - with patch( - "homeassistant.helpers.device_registry.async_entries_for_config_entry" - ): - base_child_info = Mock(HubChildBaseInfo) - base_child_info.model = model - base_child_info.device_id = "123ABC" - base_child_info.nickname = "123ABC" - base_child_info.firmware_version = "1.2.3" - base_child_info.hardware_version = "hw1.0" - - hub = TapoHub(entry=self.config, hub=self.hub_device) - result = await hub.setup_children( - hass=self.hass, - registry=self.registry, - device_list=[base_child_info], - polling_rate=self.polling_rate, - ) - - assert len(result) == 1 - assert type(result[0].device) is expected_type - - @pytest.mark.asyncio - @pytest.mark.parametrize("children_count", [10, 50, 55, 101]) - async def test_setup_all_children(self, children_count: int): - with patch( - "homeassistant.helpers.update_coordinator.DataUpdateCoordinator.async_config_entry_first_refresh" - ): - with patch( - "homeassistant.helpers.device_registry.DeviceRegistry.async_get_or_create" - ): - with patch( - "homeassistant.helpers.device_registry.async_entries_for_config_entry" - ): - children = [] - for i in range(0, children_count): - mock = Mock(HubChildBaseInfo) - mock.model = "T110" - mock.device_id = f"123ABC{i}" - mock.nickname = f"123ABC{i}" - mock.firmware_version = f"1.2.{i}" - mock.hardware_version = f"hw{i}.0" - children.append(mock) - - hub = TapoHub(entry=self.config, hub=self.hub_device) - result = await hub.setup_children( - hass=self.hass, - registry=self.registry, - device_list=children, - polling_rate=self.polling_rate, - ) - - assert len(result) == children_count +# from datetime import timedelta +# from unittest.mock import Mock +# from unittest.mock import patch +# +# import pytest +# from custom_components.tapo.hub.hass_tapo_hub import TapoHub +# from homeassistant.config_entries import ConfigEntry +# from homeassistant.core import HomeAssistant +# from homeassistant.helpers.device_registry import DeviceRegistry +# from plugp100.api.hub.hub_device import HubDevice +# from plugp100.api.hub.ke100_device import KE100Device +# from plugp100.api.hub.s200b_device import S200ButtonDevice +# from plugp100.api.hub.switch_child_device import SwitchChildDevice +# from plugp100.api.hub.t100_device import T100MotionSensor +# from plugp100.api.hub.t110_device import T110SmartDoor +# from plugp100.api.hub.t31x_device import T31Device +# from plugp100.responses.hub_childs.hub_child_base_info import HubChildBaseInfo +# +# +# class TestTapoHub: +# config = Mock(ConfigEntry) +# hub_device = Mock(HubDevice) +# hass = Mock(HomeAssistant) +# registry = Mock(DeviceRegistry) +# polling_rate = Mock(timedelta) +# +# @pytest.mark.asyncio +# @pytest.mark.parametrize( +# "model, expected_type", +# [ +# ("KE100", KE100Device), +# ("T31", T31Device), +# ("T110", T110SmartDoor), +# ("S200", S200ButtonDevice), +# ("T100", T100MotionSensor), +# ("S220", SwitchChildDevice), +# ("S210", SwitchChildDevice), +# ], +# ) +# async def test_setup_child_coordinators_should_create_correct_types( +# self, model: str, expected_type: type +# ): +# with patch( +# "homeassistant.helpers.update_coordinator.DataUpdateCoordinator.async_config_entry_first_refresh" +# ): +# with patch( +# "homeassistant.helpers.device_registry.DeviceRegistry.async_get_or_create" +# ): +# with patch( +# "homeassistant.helpers.device_registry.async_entries_for_config_entry" +# ): +# base_child_info = Mock(HubChildBaseInfo) +# base_child_info.model = model +# base_child_info.device_id = "123ABC" +# base_child_info.nickname = "123ABC" +# base_child_info.firmware_version = "1.2.3" +# base_child_info.hardware_version = "hw1.0" +# +# hub = TapoHub(entry=self.config, hub=self.hub_device) +# result = await hub.setup_children( +# hass=self.hass, +# registry=self.registry, +# device_list=[base_child_info], +# polling_rate=self.polling_rate, +# ) +# +# assert len(result) == 1 +# assert type(result[0].device) is expected_type +# +# @pytest.mark.asyncio +# @pytest.mark.parametrize("children_count", [10, 50, 55, 101]) +# async def test_setup_all_children(self, children_count: int): +# with patch( +# "homeassistant.helpers.update_coordinator.DataUpdateCoordinator.async_config_entry_first_refresh" +# ): +# with patch( +# "homeassistant.helpers.device_registry.DeviceRegistry.async_get_or_create" +# ): +# with patch( +# "homeassistant.helpers.device_registry.async_entries_for_config_entry" +# ): +# children = [] +# for i in range(0, children_count): +# mock = Mock(HubChildBaseInfo) +# mock.model = "T110" +# mock.device_id = f"123ABC{i}" +# mock.nickname = f"123ABC{i}" +# mock.firmware_version = f"1.2.{i}" +# mock.hardware_version = f"hw{i}.0" +# children.append(mock) +# +# hub = TapoHub(entry=self.config, hub=self.hub_device) +# result = await hub.setup_children( +# hass=self.hass, +# registry=self.registry, +# device_list=children, +# polling_rate=self.polling_rate, +# ) +# +# assert len(result) == children_count diff --git a/tests/unit/hub/test_tapo_hub_child_coordinator.py b/tests/unit/hub/test_tapo_hub_child_coordinator.py index 81ae900..bcaf536 100644 --- a/tests/unit/hub/test_tapo_hub_child_coordinator.py +++ b/tests/unit/hub/test_tapo_hub_child_coordinator.py @@ -1,124 +1,124 @@ -from datetime import timedelta -from unittest.mock import AsyncMock -from unittest.mock import Mock -from unittest.mock import patch - -import pytest -from custom_components.tapo.hub.tapo_hub_child_coordinator import HubChildCommonState -from custom_components.tapo.hub.tapo_hub_child_coordinator import HubChildDevice -from custom_components.tapo.hub.tapo_hub_child_coordinator import ( - TapoHubChildCoordinator, -) -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant -from plugp100.api.hub.ke100_device import KE100Device -from plugp100.api.hub.ke100_device import KE100DeviceState -from plugp100.api.hub.s200b_device import S200BDeviceState -from plugp100.api.hub.s200b_device import S200ButtonDevice -from plugp100.api.hub.switch_child_device import SwitchChildDevice -from plugp100.api.hub.switch_child_device import SwitchChildDeviceState -from plugp100.api.hub.t100_device import T100MotionSensor -from plugp100.api.hub.t100_device import T100MotionSensorState -from plugp100.api.hub.t110_device import T110SmartDoor -from plugp100.api.hub.t110_device import T110SmartDoorState -from plugp100.api.hub.t31x_device import T31Device -from plugp100.api.hub.t31x_device import T31DeviceState -from plugp100.api.hub.water_leak_device import LeakDeviceState -from plugp100.api.hub.water_leak_device import WaterLeakSensor -from plugp100.common.functional.tri import Try - - -class TestTapoHubChildCoordinator: - config = Mock(ConfigEntry) - hass = Mock(HomeAssistant) - polling_rate = Mock(timedelta) - - @pytest.mark.asyncio - @pytest.mark.parametrize( - "device_type, expected_device_state, expected_nickname", - [ - (KE100Device, Mock(KE100DeviceState), "ke100"), - (T110SmartDoor, Mock(T110SmartDoorState), "t110"), - (WaterLeakSensor, Mock(LeakDeviceState), "leaky"), - (T100MotionSensor, Mock(T100MotionSensorState), "t100"), - ], - ) - async def test_setup_child_coordinators_should_create_correct_types( - self, - device_type: HubChildDevice, - expected_device_state: HubChildCommonState, - expected_nickname: str, - ): - with patch( - "homeassistant.helpers.update_coordinator.DataUpdateCoordinator.async_config_entry_first_refresh" - ): - device = AsyncMock(device_type) - - device.get_device_state.return_value = Try.of(expected_device_state) - expected_device_state.return_value.nickname = expected_nickname - - hub_child_coordinator = TapoHubChildCoordinator( - hass=self.hass, device=device, polling_interval=self.polling_rate - ) - await hub_child_coordinator._update_state() - - state_values = list(hub_child_coordinator._states.values()) - - assert len(state_values) == 1 - assert state_values[0].nickname == expected_nickname - - @pytest.mark.asyncio - @pytest.mark.parametrize( - "device_type, expected_device_state, expected_nickname", - [ - (S200ButtonDevice, Mock(S200BDeviceState), "s200"), - (SwitchChildDevice, Mock(SwitchChildDeviceState), "s220"), - (SwitchChildDevice, Mock(SwitchChildDeviceState), "s210"), - ], - ) - async def test_setup_child_coordinators_should_create_correct_types_get_device_info( - self, - device_type: HubChildDevice, - expected_device_state: HubChildCommonState, - expected_nickname: str, - ): - with patch( - "homeassistant.helpers.update_coordinator.DataUpdateCoordinator.async_config_entry_first_refresh" - ): - device = AsyncMock(device_type) - - device.get_device_info.return_value = Try.of(expected_device_state) - expected_device_state.return_value.nickname = expected_nickname - - hub_child_coordinator = TapoHubChildCoordinator( - hass=self.hass, device=device, polling_interval=self.polling_rate - ) - await hub_child_coordinator._update_state() - - state_values = list(hub_child_coordinator._states.values()) - - assert len(state_values) == 1 - assert state_values[0].nickname == expected_nickname - - @pytest.mark.asyncio - async def test_setup_child_coordinators_t31_device(self): - with patch( - "homeassistant.helpers.update_coordinator.DataUpdateCoordinator.async_config_entry_first_refresh" - ): - device = AsyncMock(T31Device) - expected_device_state = Mock(T31DeviceState) - expected_nickname = "t31" - - device.get_device_state.return_value = Try.of(expected_device_state) - expected_device_state.return_value.nickname = expected_nickname - - hub_child_coordinator = TapoHubChildCoordinator( - hass=self.hass, device=device, polling_interval=self.polling_rate - ) - await hub_child_coordinator._update_state() - - state_values = list(hub_child_coordinator._states.values()) - - assert len(state_values) == 2 - assert state_values[0].nickname == expected_nickname - device.get_temperature_humidity_records.assert_called_once() +# from datetime import timedelta +# from unittest.mock import AsyncMock +# from unittest.mock import Mock +# from unittest.mock import patch +# +# import pytest +# from custom_components.tapo.hub.tapo_hub_child_coordinator import HubChildCommonState +# from custom_components.tapo.hub.tapo_hub_child_coordinator import HubChildDevice +# from custom_components.tapo.hub.tapo_hub_child_coordinator import ( +# TapoHubChildCoordinator, +# ) +# from homeassistant.config_entries import ConfigEntry +# from homeassistant.core import HomeAssistant +# from plugp100.api.hub.ke100_device import KE100Device +# from plugp100.api.hub.ke100_device import KE100DeviceState +# from plugp100.api.hub.s200b_device import S200BDeviceState +# from plugp100.api.hub.s200b_device import S200ButtonDevice +# from plugp100.api.hub.switch_child_device import SwitchChildDevice +# from plugp100.api.hub.switch_child_device import SwitchChildDeviceState +# from plugp100.api.hub.t100_device import T100MotionSensor +# from plugp100.api.hub.t100_device import T100MotionSensorState +# from plugp100.api.hub.t110_device import T110SmartDoor +# from plugp100.api.hub.t110_device import T110SmartDoorState +# from plugp100.api.hub.t31x_device import T31Device +# from plugp100.api.hub.t31x_device import T31DeviceState +# from plugp100.api.hub.water_leak_device import LeakDeviceState +# from plugp100.api.hub.water_leak_device import WaterLeakSensor +# from plugp100.common.functional.tri import Try +# +# +# class TestTapoHubChildCoordinator: +# config = Mock(ConfigEntry) +# hass = Mock(HomeAssistant) +# polling_rate = Mock(timedelta) +# +# @pytest.mark.asyncio +# @pytest.mark.parametrize( +# "device_type, expected_device_state, expected_nickname", +# [ +# (KE100Device, Mock(KE100DeviceState), "ke100"), +# (T110SmartDoor, Mock(T110SmartDoorState), "t110"), +# (WaterLeakSensor, Mock(LeakDeviceState), "leaky"), +# (T100MotionSensor, Mock(T100MotionSensorState), "t100"), +# ], +# ) +# async def test_setup_child_coordinators_should_create_correct_types( +# self, +# device_type: HubChildDevice, +# expected_device_state: HubChildCommonState, +# expected_nickname: str, +# ): +# with patch( +# "homeassistant.helpers.update_coordinator.DataUpdateCoordinator.async_config_entry_first_refresh" +# ): +# device = AsyncMock(device_type) +# +# device.get_device_state.return_value = Try.of(expected_device_state) +# expected_device_state.return_value.nickname = expected_nickname +# +# hub_child_coordinator = TapoHubChildCoordinator( +# hass=self.hass, device=device, polling_interval=self.polling_rate +# ) +# await hub_child_coordinator._update_state() +# +# state_values = list(hub_child_coordinator._states.values()) +# +# assert len(state_values) == 1 +# assert state_values[0].nickname == expected_nickname +# +# @pytest.mark.asyncio +# @pytest.mark.parametrize( +# "device_type, expected_device_state, expected_nickname", +# [ +# (S200ButtonDevice, Mock(S200BDeviceState), "s200"), +# (SwitchChildDevice, Mock(SwitchChildDeviceState), "s220"), +# (SwitchChildDevice, Mock(SwitchChildDeviceState), "s210"), +# ], +# ) +# async def test_setup_child_coordinators_should_create_correct_types_get_device_info( +# self, +# device_type: HubChildDevice, +# expected_device_state: HubChildCommonState, +# expected_nickname: str, +# ): +# with patch( +# "homeassistant.helpers.update_coordinator.DataUpdateCoordinator.async_config_entry_first_refresh" +# ): +# device = AsyncMock(device_type) +# +# device.get_device_info.return_value = Try.of(expected_device_state) +# expected_device_state.return_value.nickname = expected_nickname +# +# hub_child_coordinator = TapoHubChildCoordinator( +# hass=self.hass, device=device, polling_interval=self.polling_rate +# ) +# await hub_child_coordinator._update_state() +# +# state_values = list(hub_child_coordinator._states.values()) +# +# assert len(state_values) == 1 +# assert state_values[0].nickname == expected_nickname +# +# @pytest.mark.asyncio +# async def test_setup_child_coordinators_t31_device(self): +# with patch( +# "homeassistant.helpers.update_coordinator.DataUpdateCoordinator.async_config_entry_first_refresh" +# ): +# device = AsyncMock(T31Device) +# expected_device_state = Mock(T31DeviceState) +# expected_nickname = "t31" +# +# device.get_device_state.return_value = Try.of(expected_device_state) +# expected_device_state.return_value.nickname = expected_nickname +# +# hub_child_coordinator = TapoHubChildCoordinator( +# hass=self.hass, device=device, polling_interval=self.polling_rate +# ) +# await hub_child_coordinator._update_state() +# +# state_values = list(hub_child_coordinator._states.values()) +# +# assert len(state_values) == 2 +# assert state_values[0].nickname == expected_nickname +# device.get_temperature_humidity_records.assert_called_once() diff --git a/tests/unit/test_const.py b/tests/unit/test_const.py deleted file mode 100644 index daed74b..0000000 --- a/tests/unit/test_const.py +++ /dev/null @@ -1,16 +0,0 @@ -from custom_components.tapo.const import PLATFORMS -from homeassistant.const import Platform - - -class TestHubPlatforms: - def test_hub_platforms(self): - expected_platforms = [ - Platform.SIREN, - Platform.BINARY_SENSOR, - Platform.SENSOR, - Platform.SWITCH, - Platform.CLIMATE, - Platform.NUMBER, - ] - - assert PLATFORMS == expected_platforms