From 381952ea8ab2be41c9b7c42f14f6cf8c1a6e452b Mon Sep 17 00:00:00 2001 From: Xavier Moreno Date: Sat, 3 Jul 2021 18:43:32 +0200 Subject: [PATCH 1/3] feat(entity_groups): add support for more Group Integrations Up until now ControllerX officially supported Group Integration only, but now it covers others, like Light Group Integration for example. related to #330 --- README.md | 2 +- RELEASE_NOTES.md | 14 +- apps/controllerx/cx_core/controller.py | 26 +++- .../cx_core/feature_support/__init__.py | 7 +- .../cx_core/integration/__init__.py | 2 +- .../cx_core/integration/lutron_caseta.py | 2 +- apps/controllerx/cx_core/integration/mqtt.py | 2 +- apps/controllerx/cx_core/integration/state.py | 2 +- apps/controllerx/cx_core/integration/z2m.py | 4 +- apps/controllerx/cx_core/integration/zha.py | 2 +- .../cx_core/type/cover_controller.py | 6 +- .../cx_core/type/light_controller.py | 17 ++- .../cx_core/type/media_player_controller.py | 8 +- apps/controllerx/cx_core/type_controller.py | 122 ++++++++++++------ docs/advanced/entity-groups.md | 37 ++++++ docs/faq.md | 4 +- docs/index.md | 1 + tests/conftest.py | 2 +- tests/integ_tests/example_config/config.yaml | 2 +- .../example_config_hold_test.yaml | 4 +- .../example_config_toggle_colortemp_test.yaml | 4 +- .../example_config_toggle_xycolor_test.yaml | 4 +- .../example_config/group_light_test.yaml | 9 ++ .../toggle_called_twice_test.yaml | 2 +- tests/integ_tests/integ_test.py | 12 +- tests/unit_tests/conftest.py | 2 +- tests/unit_tests/cx_core/controller_test.py | 2 +- .../cx_core/custom_controller_test.py | 6 +- .../feature_support/cover_support_test.py | 2 +- .../feature_support/feature_support_test.py | 4 +- .../feature_support/light_support_test.py | 2 +- .../media_player_support_test.py | 2 +- .../cx_core/type/cover_controller_test.py | 2 + .../cx_core/type/light_controller_test.py | 38 +++--- .../type/media_player_controller_test.py | 3 +- .../cx_core/type/switch_controller_test.py | 7 +- .../cx_core/type_controller_test.py | 67 +++++----- 37 files changed, 267 insertions(+), 167 deletions(-) create mode 100644 docs/advanced/entity-groups.md create mode 100644 tests/integ_tests/example_config/group_light_test.yaml diff --git a/README.md b/README.md index 1e0d2853..43f68841 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ livingroom_controller: class: E1810Controller controller: sensor.livingroom_controller_action integration: z2m - light: light.bedroom + light: light.livingroom ``` ## Documentation diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 5567c7bf..a73b23d3 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,18 +6,18 @@ _This minor change does not contain any breaking changes._ _Note: Remember to restart the AppDaemon addon/server after updating to a new version._ PRERELEASE_NOTE - -## :hammer: Fixes +## :pencil2: Features -- Clean action handle when there is an error. This will help for error logging. +- Support for Light Group integration. To know how entity groups work, read [here](https://xaviml.github.io/controllerx/advanced/entity-groups) +## :clock2: Performance +- Reduce calls to HA when entity is a group. + @@ -26,6 +26,8 @@ PRERELEASE_NOTE ## :wrench: Refactor --> + \ No newline at end of file diff --git a/apps/controllerx/cx_core/controller.py b/apps/controllerx/cx_core/controller.py index 80e7f589..921c3387 100644 --- a/apps/controllerx/cx_core/controller.py +++ b/apps/controllerx/cx_core/controller.py @@ -19,12 +19,13 @@ Tuple, TypeVar, Union, + overload, ) import appdaemon.utils as utils import cx_version -from appdaemon.plugins.hass.hassapi import Hass # type: ignore -from appdaemon.plugins.mqtt.mqttapi import Mqtt # type: ignore +from appdaemon.plugins.hass.hassapi import Hass +from appdaemon.plugins.mqtt.mqttapi import Mqtt from cx_const import ( ActionEvent, ActionFunction, @@ -85,6 +86,7 @@ class Controller(Hass, Mqtt): This is the parent Controller, all controllers must extend from this class. """ + args: Dict[str, Any] integration: Integration actions_mapping: ActionsMapping action_handles: DefaultDict[ActionEvent, Optional["Future[None]"]] @@ -222,7 +224,15 @@ def get_default_actions_mapping( raise ValueError(f"This controller does not support {integration.name}.") return actions_mapping - def get_list(self, entities: Union[List[T], T]) -> List[T]: + @overload + def get_list(self, entities: List[T]) -> List[T]: + ... + + @overload + def get_list(self, entities: T) -> List[T]: + ... + + def get_list(self, entities): if isinstance(entities, (list, tuple)): return list(entities) return [entities] @@ -307,7 +317,7 @@ async def call_service(self, service: str, **attributes) -> Optional[Any]: value = f"{value:.2f}" to_log.append(f" - {attribute}: {value}") self.log("\n".join(to_log), level="INFO", ascii_encode=False) - return await Hass.call_service(self, service, **attributes) # type: ignore + return await Hass.call_service(self, service, **attributes) @utils.sync_wrapper async def get_state( @@ -319,7 +329,9 @@ async def get_state( **kwargs, ) -> Optional[Any]: rendered_entity_id = await self.render_value(entity_id) - return await super().get_state(rendered_entity_id, attribute, default, copy, **kwargs) # type: ignore + return await super().get_state( + rendered_entity_id, attribute, default, copy, **kwargs + ) async def handle_action( self, action_key: str, extra: Optional[EventData] = None @@ -395,7 +407,7 @@ async def call_action( if delay > 0: handle = self.action_delay_handles[action_key] if handle is not None: - await self.cancel_timer(handle) # type: ignore + await self.cancel_timer(handle) self.log( f"🕒 Running action(s) from `{action_key}` in {delay} seconds", level="INFO", @@ -403,7 +415,7 @@ async def call_action( ) new_handle = await self.run_in( self.action_timer_callback, delay, action_key=action_key, extra=extra - ) # type: ignore + ) self.action_delay_handles[action_key] = new_handle else: await self.action_timer_callback({"action_key": action_key, "extra": extra}) diff --git a/apps/controllerx/cx_core/feature_support/__init__.py b/apps/controllerx/cx_core/feature_support/__init__.py index dbb02111..a3e0e584 100644 --- a/apps/controllerx/cx_core/feature_support/__init__.py +++ b/apps/controllerx/cx_core/feature_support/__init__.py @@ -6,19 +6,16 @@ class FeatureSupport: - entity_id: str controller: "TypeController" update_supported_features: bool _supported_features: Optional[int] def __init__( self, - entity_id: str, controller: "TypeController", supported_features: Optional[int] = None, update_supported_features=False, ) -> None: - self.entity_id = entity_id self.controller = controller self._supported_features = supported_features self.update_supported_features = update_supported_features @@ -27,13 +24,13 @@ def __init__( async def supported_features(self) -> int: if self._supported_features is None or self.update_supported_features: bitfield: str = await self.controller.get_entity_state( - self.entity_id, attribute="supported_features" + attribute="supported_features" ) if bitfield is not None: self._supported_features = int(bitfield) else: raise ValueError( - f"`supported_features` could not be read from `{self.entity_id}`. Entity might not be available." + f"`supported_features` could not be read from `{self.controller.entity}`. Entity might not be available." ) return self._supported_features diff --git a/apps/controllerx/cx_core/integration/__init__.py b/apps/controllerx/cx_core/integration/__init__.py index 7a2e2b2f..418c4401 100644 --- a/apps/controllerx/cx_core/integration/__init__.py +++ b/apps/controllerx/cx_core/integration/__init__.py @@ -46,7 +46,7 @@ def _all_integration_subclasses( subclasses = set(cls_.__subclasses__()).union( [s for c in cls_.__subclasses__() for s in _all_integration_subclasses(c)] ) - return list(subclasses) # type: ignore + return list(subclasses) def get_integrations(controller, kwargs) -> List[Integration]: diff --git a/apps/controllerx/cx_core/integration/lutron_caseta.py b/apps/controllerx/cx_core/integration/lutron_caseta.py index 6702ec44..7015d073 100644 --- a/apps/controllerx/cx_core/integration/lutron_caseta.py +++ b/apps/controllerx/cx_core/integration/lutron_caseta.py @@ -1,6 +1,6 @@ from typing import Optional -from appdaemon.plugins.hass.hassapi import Hass # type: ignore +from appdaemon.plugins.hass.hassapi import Hass from cx_const import DefaultActionsMapping from cx_core.integration import EventData, Integration diff --git a/apps/controllerx/cx_core/integration/mqtt.py b/apps/controllerx/cx_core/integration/mqtt.py index 51a99a13..ce16b79b 100644 --- a/apps/controllerx/cx_core/integration/mqtt.py +++ b/apps/controllerx/cx_core/integration/mqtt.py @@ -1,6 +1,6 @@ from typing import Optional -from appdaemon.plugins.mqtt.mqttapi import Mqtt # type: ignore +from appdaemon.plugins.mqtt.mqttapi import Mqtt from cx_const import DefaultActionsMapping from cx_core.integration import EventData, Integration diff --git a/apps/controllerx/cx_core/integration/state.py b/apps/controllerx/cx_core/integration/state.py index ac51b090..9eb339e2 100644 --- a/apps/controllerx/cx_core/integration/state.py +++ b/apps/controllerx/cx_core/integration/state.py @@ -1,6 +1,6 @@ from typing import Optional -from appdaemon.plugins.hass.hassapi import Hass # type: ignore +from appdaemon.plugins.hass.hassapi import Hass from cx_const import DefaultActionsMapping from cx_core.integration import Integration diff --git a/apps/controllerx/cx_core/integration/z2m.py b/apps/controllerx/cx_core/integration/z2m.py index 11255e2c..eb238619 100644 --- a/apps/controllerx/cx_core/integration/z2m.py +++ b/apps/controllerx/cx_core/integration/z2m.py @@ -1,8 +1,8 @@ import json from typing import Optional -from appdaemon.plugins.hass.hassapi import Hass # type: ignore -from appdaemon.plugins.mqtt.mqttapi import Mqtt # type: ignore +from appdaemon.plugins.hass.hassapi import Hass +from appdaemon.plugins.mqtt.mqttapi import Mqtt from cx_const import DefaultActionsMapping from cx_core.integration import EventData, Integration diff --git a/apps/controllerx/cx_core/integration/zha.py b/apps/controllerx/cx_core/integration/zha.py index e6e64cc3..960bbc88 100644 --- a/apps/controllerx/cx_core/integration/zha.py +++ b/apps/controllerx/cx_core/integration/zha.py @@ -1,6 +1,6 @@ from typing import Optional -from appdaemon.plugins.hass.hassapi import Hass # type: ignore +from appdaemon.plugins.hass.hassapi import Hass from cx_const import DefaultActionsMapping from cx_core.integration import EventData, Integration diff --git a/apps/controllerx/cx_core/type/cover_controller.py b/apps/controllerx/cx_core/type/cover_controller.py index 4c32f240..d982092b 100644 --- a/apps/controllerx/cx_core/type/cover_controller.py +++ b/apps/controllerx/cx_core/type/cover_controller.py @@ -56,7 +56,7 @@ async def open(self) -> None: await self.call_service("cover/open_cover", entity_id=self.entity.name) else: self.log( - f"⚠️ `{self.entity.name}` does not support SET_COVER_POSITION or OPEN", + f"⚠️ `{self.entity}` does not support SET_COVER_POSITION or OPEN", level="WARNING", ascii_encode=False, ) @@ -73,7 +73,7 @@ async def close(self) -> None: await self.call_service("cover/close_cover", entity_id=self.entity.name) else: self.log( - f"⚠️ `{self.entity.name}` does not support SET_COVER_POSITION or CLOSE", + f"⚠️ `{self.entity}` does not support SET_COVER_POSITION or CLOSE", level="WARNING", ascii_encode=False, ) @@ -84,7 +84,7 @@ async def stop(self) -> None: @action async def toggle(self, action: Callable) -> None: - cover_state = await self.get_entity_state(self.entity.name) + cover_state = await self.get_entity_state() if cover_state == "opening" or cover_state == "closing": await self.stop() else: diff --git a/apps/controllerx/cx_core/type/light_controller.py b/apps/controllerx/cx_core/type/light_controller.py index 43670f7c..7ad09d44 100644 --- a/apps/controllerx/cx_core/type/light_controller.py +++ b/apps/controllerx/cx_core/type/light_controller.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional, Type, Union +from typing import Any, Dict, List, Optional, Type, Union from cx_const import Light, PredefinedActionsMapping from cx_core.color_helper import get_color_wheel @@ -34,8 +34,13 @@ class LightEntity(Entity): color_mode: ColorMode - def __init__(self, name: str, color_mode: ColorMode = "auto") -> None: - super().__init__(name) + def __init__( + self, + name: str, + entities: Optional[List[str]] = None, + color_mode: ColorMode = "auto", + ) -> None: + super().__init__(name, entities) self.color_mode = color_mode @@ -532,11 +537,11 @@ async def get_value_attribute(self, attribute: str) -> Union[float, int]: or attribute == LightController.ATTRIBUTE_WHITE_VALUE or attribute == LightController.ATTRIBUTE_COLOR_TEMP ): - value = await self.get_entity_state(self.entity.name, attribute) + value = await self.get_entity_state(attribute=attribute) if value is None: raise ValueError( f"Value for `{attribute}` attribute could not be retrieved " - f"from `{self.entity.name}`. " + f"from `{self.entity.main}`. " "Check the FAQ to know more about this error: " "https://xaviml.github.io/controllerx/faq" ) @@ -565,7 +570,7 @@ async def before_action(self, action: str, *args, **kwargs) -> bool: to_return = True if action in ("click", "hold"): attribute, direction = args - light_state: str = await self.get_entity_state(self.entity.name) + light_state: str = await self.get_entity_state() self.smooth_power_on_check = self.check_smooth_power_on( attribute, direction, light_state ) diff --git a/apps/controllerx/cx_core/type/media_player_controller.py b/apps/controllerx/cx_core/type/media_player_controller.py index 413c2f6c..8fe4104b 100644 --- a/apps/controllerx/cx_core/type/media_player_controller.py +++ b/apps/controllerx/cx_core/type/media_player_controller.py @@ -45,12 +45,12 @@ def get_predefined_actions_mapping(self) -> PredefinedActionsMapping: @action async def change_source_list(self, direction: str) -> None: - entity_states = await self.get_entity_state(self.entity.name, attribute="all") + entity_states = await self.get_entity_state(attribute="all") entity_attributes = entity_states["attributes"] source_list = entity_attributes.get("source_list") if len(source_list) == 0 or source_list is None: self.log( - f"⚠️ There is no `source_list` parameter in `{self.entity.name}`", + f"⚠️ There is no `source_list` parameter in `{self.entity}`", level="WARNING", ascii_encode=False, ) @@ -114,9 +114,7 @@ async def hold(self, direction: str) -> None: # type: ignore await super().hold(direction) async def prepare_volume_change(self) -> None: - volume_level = await self.get_entity_state( - self.entity.name, attribute="volume_level" - ) + volume_level = await self.get_entity_state(attribute="volume_level") if volume_level is not None: self.volume_level = volume_level diff --git a/apps/controllerx/cx_core/type_controller.py b/apps/controllerx/cx_core/type_controller.py index 79f77ca9..82377070 100644 --- a/apps/controllerx/cx_core/type_controller.py +++ b/apps/controllerx/cx_core/type_controller.py @@ -1,5 +1,5 @@ import abc -from typing import Any, Generic, List, Optional, Type, TypeVar, Union +from typing import Any, Dict, Generic, List, Optional, Type, TypeVar, Union from cx_core.controller import Controller from cx_core.feature_support import FeatureSupport @@ -9,13 +9,33 @@ class Entity: name: str + entities: List[str] - def __init__(self, name: str) -> None: + def __init__( + self, name: str, entities: Optional[List[str]] = None, **kwargs: Dict[str, Any] + ) -> None: self.name = name + self.set_entities(entities) + + @property + def main(self) -> str: + return self.entities[0] + + @property + def is_group(self) -> bool: + return self.entities[0] != self.name + + def set_entities(self, value: Optional[List[str]] = None) -> None: + self.entities = value if value is not None else [self.name] @classmethod - def instantiate(cls: Type[EntityType], **params) -> EntityType: - return cls(**params) + def instantiate( + cls: Type[EntityType], name: str, entities: Optional[List[str]] = None, **params + ) -> EntityType: + return cls(name=name, entities=entities, **params) + + def __str__(self) -> str: + return self.name if not self.is_group else f"{self.name}({self.entities})" class TypeController(Controller, abc.ABC, Generic[EntityType]): @@ -23,6 +43,7 @@ class TypeController(Controller, abc.ABC, Generic[EntityType]): domains: List[str] entity_arg: str entity: EntityType + update_supported_features: bool feature_support: FeatureSupport async def init(self) -> None: @@ -30,12 +51,14 @@ async def init(self) -> None: raise ValueError( f"{self.__class__.__name__} class needs the `{self.entity_arg}` attribute" ) - self.entity = self.get_entity(self.args[self.entity_arg]) # type: ignore - await self.check_domain(self.entity.name) - update_supported_features = self.args.get("update_supported_features", False) - supported_features = self.args.get("supported_features") + self.entity = await self._get_entity(self.args[self.entity_arg]) # type: ignore + self._check_domain(self.entity) + self.update_supported_features = self.args.get( + "update_supported_features", False + ) + supported_features: Optional[int] = self.args.get("supported_features") self.feature_support = FeatureSupport( - self.entity.name, self, supported_features, update_supported_features + self, supported_features, self.update_supported_features ) await super().init() @@ -43,47 +66,66 @@ async def init(self) -> None: def _get_entity_type(self) -> Type[Entity]: raise NotImplementedError - def get_entity(self, entity: Union[str, dict]) -> Entity: + async def _get_entities(self, entity_name: str) -> Optional[List[str]]: + entities: Optional[Union[str, List[str]]] = await self.get_state( + entity_name, attribute="entity_id" + ) + self.log( + f"Entities from `{entity_name}` (entity_id attribute): `{entities}`", + level="DEBUG", + ) + # When the entity is not a group, this attribute returns the entity name + if isinstance(entities, str): + return None + if entities is not None and len(entities) == 0: + raise ValueError(f"`{entity_name}` does not have any entities registered.") + return entities + + async def _get_entity(self, entity: Union[str, dict]) -> Entity: + entity_args: Dict + entity_name: str if isinstance(entity, str): - return self._get_entity_type().instantiate(name=entity) + entity_name = entity + entity_args = {} elif isinstance(entity, dict): - return self._get_entity_type().instantiate(**entity) + entity_name = entity["name"] + entity_args = {key: value for key, value in entity.items() if key != "name"} else: raise ValueError( f"Type {type(entity)} is not supported for `{self.entity_arg}` attribute" ) + entities = await self._get_entities(entity_name) + return self._get_entity_type().instantiate( + name=entity_name, entities=entities, **entity_args + ) - async def check_domain(self, entity_name: str) -> None: - if self.contains_templating(entity_name): + def _check_domain(self, entity: Entity) -> None: + if self.contains_templating(entity.name): return - elif entity_name.startswith("group."): - entities: List[str] = await self.get_state(entity_name, attribute="entity_id") # type: ignore - same_domain = all( - ( - any(elem.startswith(domain + ".") for domain in self.domains) - for elem in entities - ) + same_domain = all( + ( + any(elem.startswith(domain + ".") for domain in self.domains) + for elem in entity.entities ) - if not same_domain: - raise ValueError( - f"All entities from '{entity_name}' must be from one " + ) + if not same_domain: + if entity.is_group: + error_msg = ( + f"All the subentities from {entity} must be from one " f"of the following domains {self.domains} (e.g. {self.domains[0]}.bedroom)" ) - elif not any(entity_name.startswith(domain + ".") for domain in self.domains): - raise ValueError( - f"'{entity_name}' must be from one of the following domains " - f"{self.domains} (e.g. {self.domains[0]}.bedroom)" - ) - - async def get_entity_state( - self, entity: str, attribute: Optional[str] = None - ) -> Any: - if entity.startswith("group."): - entities: List[str] = await self.get_state(entity, attribute="entity_id") # type: ignore - if len(entities) == 0: - raise ValueError( - f"The group `{entity}` does not have any entities registered." + else: + error_msg = ( + f"'{entity}' must be from one " + f"of the following domains {self.domains} (e.g. {self.domains[0]}.bedroom)" ) - entity = entities[0] - out = await self.get_state(entity, attribute=attribute) # type: ignore + raise ValueError(error_msg) + + async def get_entity_state(self, attribute: Optional[str] = None) -> Any: + entity = self.entity.main + if self.update_supported_features: + entities = await self._get_entities(self.entity.name) + self.entity.set_entities(entities) + entity = self.entity.entities[0] + out = await self.get_state(entity, attribute=attribute) return out diff --git a/docs/advanced/entity-groups.md b/docs/advanced/entity-groups.md new file mode 100644 index 00000000..5637e4ea --- /dev/null +++ b/docs/advanced/entity-groups.md @@ -0,0 +1,37 @@ +--- +title: Entity groups +layout: page +--- + +_This is supported from ControllerX v4.14.0_ + +ControllerX allow for Entity Controllers (LightController, MediaPlayerController, CoverController, etc) to work with grouped entities. + +All is needed is an entity with `entity_id` attribute with a list of entities controlled by the grouped entity. For example, we can use a group entity from [Group Integration](https://www.home-assistant.io/integrations/group/), or from [Light Group Integration](https://www.home-assistant.io/integrations/light.group/). ControllerX will read attribute from the main entity (the first one from the list), but will run the actions on the grouped entity. + +Let's imagine we have a Light Group entity (`light.livingroom`): + +```yaml +light: + - platform: group + name: livingroom + entities: + - light.livingroom_1 + - light.livingroom_2 + - light.livingroom_3 +``` + +Then, we could for example configure the following in apps.yaml file: + +```yaml +example_app: + module: controllerx + class: E1810Controller + controller: sensor.livingroom_controller_action + integration: z2m + light: light.livingroom +``` + +`light.livingroom_1` will be the main light that ControllerX will read from, but `light.livingroom` will be the grouped entity that ControllerX will perform the actions. + +For example, if `light.livingroom_1` does not support `brightness`, but `light.livingroom_2` and `light.livingroom_3` do, then the configuration will not work because ControllerX will not be able to read `brightness` attribute from `light.livingroom_1`. diff --git a/docs/faq.md b/docs/faq.md index 030877e6..6eb00669 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -17,9 +17,7 @@ From the zigbee2mqtt documentation is recommended to set the `debounce` attribut #### 4. I have a group of lights and it does not work properly -HA offers different ways to group lights, even each Light integration might have the option of grouping lights (like [Hue](https://www.home-assistant.io/integrations/hue/) integration). This is why ControllerX sticks to just one official way to group lights, which is the [Group](https://www.home-assistant.io/integrations/group/) integration. This means you will need to set up a group with your lights in your `configuration.yaml`. ControllerX will know is a group of lights because it will use the `group.XXXXX` domain. Furthermore, it will take the first light as a main light, so it will take its values (brightness, color) to change the group of lights. - -This does not mean that any other integration will not work, but they might not work as expected, this is why [Group](https://www.home-assistant.io/integrations/group/) integration should be used if you want the expected ControllerX behaviour. +Please see [here](/controllerx/advanced/entity-groups) to understand how grouped entities work. #### 5. Error: "Value for X attribute could not be retrieved from light Y" diff --git a/docs/index.md b/docs/index.md index 6ac6ef54..59df4bb8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -63,6 +63,7 @@ _ControllerX_ uses an async loop to make HA call services requests (e.g. to chan - [Predefined actions](advanced/predefined-actions) - [Multiple clicks](advanced/multiple-clicks) - [Templating](advanced/templating) +- [Entity Groups](advanced/entity-groups) ## Others diff --git a/tests/conftest.py b/tests/conftest.py index b1d04e99..a329e50d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,6 @@ import asyncio -import appdaemon.plugins.hass.hassapi as hass # type: ignore +import appdaemon.plugins.hass.hassapi as hass import appdaemon.plugins.mqtt.mqttapi as mqtt import pytest from _pytest.monkeypatch import MonkeyPatch diff --git a/tests/integ_tests/example_config/config.yaml b/tests/integ_tests/example_config/config.yaml index ebd1348e..d9500cfe 100644 --- a/tests/integ_tests/example_config/config.yaml +++ b/tests/integ_tests/example_config/config.yaml @@ -3,4 +3,4 @@ livingroom_controller: class: E1810Controller controller: sensor.livingroom_controller_action integration: z2m - light: light.bedroom + light: light.livingroom diff --git a/tests/integ_tests/example_config/example_config_hold_test.yaml b/tests/integ_tests/example_config/example_config_hold_test.yaml index 14da05d5..d7049ee8 100644 --- a/tests/integ_tests/example_config/example_config_hold_test.yaml +++ b/tests/integ_tests/example_config/example_config_hold_test.yaml @@ -6,11 +6,11 @@ fired_actions: [brightness_up_hold, 0.450, brightness_up_release] expected_calls: - service: light/turn_on data: - entity_id: light.bedroom + entity_id: light.livingroom transition: 0.35 brightness: 75.4 - service: light/turn_on data: - entity_id: light.bedroom + entity_id: light.livingroom transition: 0.35 brightness: 100.8 diff --git a/tests/integ_tests/example_config/example_config_toggle_colortemp_test.yaml b/tests/integ_tests/example_config/example_config_toggle_colortemp_test.yaml index 92d58a45..33967299 100644 --- a/tests/integ_tests/example_config/example_config_toggle_colortemp_test.yaml +++ b/tests/integ_tests/example_config/example_config_toggle_colortemp_test.yaml @@ -5,10 +5,10 @@ fired_actions: [toggle, 1.0, toggle_hold] expected_calls: - service: light/toggle data: - entity_id: light.bedroom + entity_id: light.livingroom - service: light/turn_on data: - entity_id: light.bedroom + entity_id: light.livingroom color_temp: 370 brightness: 255 expected_calls_count: 2 diff --git a/tests/integ_tests/example_config/example_config_toggle_xycolor_test.yaml b/tests/integ_tests/example_config/example_config_toggle_xycolor_test.yaml index 620c32e1..5ba4be8c 100644 --- a/tests/integ_tests/example_config/example_config_toggle_xycolor_test.yaml +++ b/tests/integ_tests/example_config/example_config_toggle_xycolor_test.yaml @@ -5,10 +5,10 @@ fired_actions: [toggle, 1.0, toggle_hold] expected_calls: - service: light/toggle data: - entity_id: light.bedroom + entity_id: light.livingroom - service: light/turn_on data: - entity_id: light.bedroom + entity_id: light.livingroom xy_color: !!python/tuple [0.323, 0.329] brightness: 255 expected_calls_count: 2 diff --git a/tests/integ_tests/example_config/group_light_test.yaml b/tests/integ_tests/example_config/group_light_test.yaml new file mode 100644 index 00000000..bc43e8d2 --- /dev/null +++ b/tests/integ_tests/example_config/group_light_test.yaml @@ -0,0 +1,9 @@ +entity_state_attributes: + supported_features: 191 +entity_entities: ["light.bedroom_1", "light.bedroom_2"] +entity_state: "off" +fired_actions: [toggle] +expected_calls: +- service: light/toggle + data: + entity_id: light.livingroom diff --git a/tests/integ_tests/example_config/toggle_called_twice_test.yaml b/tests/integ_tests/example_config/toggle_called_twice_test.yaml index 5affebe6..93017b93 100644 --- a/tests/integ_tests/example_config/toggle_called_twice_test.yaml +++ b/tests/integ_tests/example_config/toggle_called_twice_test.yaml @@ -7,4 +7,4 @@ fired_actions: [toggle, toggle] expected_calls: - service: light/toggle data: - entity_id: light.bedroom + entity_id: light.livingroom diff --git a/tests/integ_tests/integ_test.py b/tests/integ_tests/integ_test.py index 625b4e0d..019e2c4b 100644 --- a/tests/integ_tests/integ_test.py +++ b/tests/integ_tests/integ_test.py @@ -1,15 +1,15 @@ import asyncio import glob from pathlib import Path -from typing import Any, Dict +from typing import Any, Dict, Optional import pytest import yaml -from appdaemon.plugins.hass.hassapi import Hass # type: ignore +from appdaemon.plugins.hass.hassapi import Hass from cx_core.type_controller import TypeController from pytest_mock.plugin import MockerFixture -from tests.test_utils import get_controller +from tests.test_utils import fake_fn, get_controller def get_integ_tests(): @@ -30,7 +30,7 @@ def read_config_yaml(file_name): def get_fake_entity_states(entity_state, entity_state_attributes): - async def inner(entity_id, attribute=None): + async def inner(attribute: Optional[str] = None): if attribute is not None and attribute in entity_state_attributes: return entity_state_attributes[attribute] return entity_state @@ -48,6 +48,7 @@ async def test_integ_configs( ): entity_state_attributes = data.get("entity_state_attributes", {}) entity_state = data.get("entity_state", None) + entity_entities = data.get("entity_entities", None) # Used for group entities fired_actions = data.get("fired_actions", []) render_template_response = data.get("render_template_response") extra = data.get("extra") @@ -69,6 +70,9 @@ async def test_integ_configs( fake_entity_states = get_fake_entity_states( entity_state, entity_state_attributes ) + mocker.patch.object( + controller, "get_state", fake_fn(entity_entities, async_=True) + ) mocker.patch.object(controller, "get_entity_state", fake_entity_states) call_service_stub = mocker.patch.object(Hass, "call_service") diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index 4c616085..96fe841c 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -2,7 +2,7 @@ import pytest from cx_core import Controller, LightController -from cx_core.action_type.base import ActionType # type: ignore +from cx_core.action_type.base import ActionType from cx_core.integration import EventData diff --git a/tests/unit_tests/cx_core/controller_test.py b/tests/unit_tests/cx_core/controller_test.py index d4a45c80..e92c5b5a 100644 --- a/tests/unit_tests/cx_core/controller_test.py +++ b/tests/unit_tests/cx_core/controller_test.py @@ -6,7 +6,7 @@ from cx_const import ActionEvent from cx_core import integration as integration_module from cx_core.action_type import ActionsMapping -from cx_core.action_type.base import ActionType # type: ignore +from cx_core.action_type.base import ActionType from cx_core.controller import Controller, action from pytest_mock.plugin import MockerFixture diff --git a/tests/unit_tests/cx_core/custom_controller_test.py b/tests/unit_tests/cx_core/custom_controller_test.py index 5fcd2f85..ff2e5dd7 100644 --- a/tests/unit_tests/cx_core/custom_controller_test.py +++ b/tests/unit_tests/cx_core/custom_controller_test.py @@ -2,7 +2,7 @@ import pytest from _pytest.monkeypatch import MonkeyPatch -from appdaemon.plugins.hass.hassapi import Hass # type: ignore +from appdaemon.plugins.hass.hassapi import Hass from cx_const import PredefinedActionsMapping from cx_core import ( CallServiceController, @@ -65,7 +65,6 @@ ) @pytest.mark.asyncio async def test_custom_controllers( - monkeypatch: MonkeyPatch, mocker: MockerFixture, custom_cls: Type[TypeController], mapping: PredefinedActionsMapping, @@ -85,7 +84,8 @@ async def test_custom_controllers( "action_delta": 0, } mocked = mocker.patch.object(sut, mock_function) - monkeypatch.setattr(sut, "get_entity_state", fake_fn(async_=True, to_return="0")) + mocker.patch.object(sut, "get_state", fake_fn(None, async_=True)) + mocker.patch.object(sut, "get_entity_state", fake_fn(async_=True, to_return="0")) # SUT await sut.initialize() diff --git a/tests/unit_tests/cx_core/feature_support/cover_support_test.py b/tests/unit_tests/cx_core/feature_support/cover_support_test.py index e1f8a712..e6db366d 100644 --- a/tests/unit_tests/cx_core/feature_support/cover_support_test.py +++ b/tests/unit_tests/cx_core/feature_support/cover_support_test.py @@ -36,7 +36,7 @@ async def test_is_supported( number: int, expected_supported_features: List[int], ): - feature_support = FeatureSupport("fake_entity", fake_type_controller, False) + feature_support = FeatureSupport(fake_type_controller) feature_support._supported_features = number for expected_supported_feature in expected_supported_features: assert await feature_support.is_supported(expected_supported_feature) diff --git a/tests/unit_tests/cx_core/feature_support/feature_support_test.py b/tests/unit_tests/cx_core/feature_support/feature_support_test.py index 02889257..d8e63c74 100644 --- a/tests/unit_tests/cx_core/feature_support/feature_support_test.py +++ b/tests/unit_tests/cx_core/feature_support/feature_support_test.py @@ -21,7 +21,7 @@ async def test_is_supported( feature: int, expected_is_supported: bool, ): - feature_support = FeatureSupport("fake_entity", fake_type_controller, False) + feature_support = FeatureSupport(fake_type_controller) feature_support._supported_features = number is_supported = await feature_support.is_supported(feature) assert is_supported == expected_is_supported @@ -45,7 +45,7 @@ async def test_not_supported( feature: int, expected_is_supported: bool, ): - feature_support = FeatureSupport("fake_entity", fake_type_controller, False) + feature_support = FeatureSupport(fake_type_controller) feature_support._supported_features = number is_supported = await feature_support.not_supported(feature) assert is_supported == expected_is_supported diff --git a/tests/unit_tests/cx_core/feature_support/light_support_test.py b/tests/unit_tests/cx_core/feature_support/light_support_test.py index 644f0d02..75da1b36 100644 --- a/tests/unit_tests/cx_core/feature_support/light_support_test.py +++ b/tests/unit_tests/cx_core/feature_support/light_support_test.py @@ -36,7 +36,7 @@ async def test_is_supported( number: int, expected_supported_features: List[int], ): - feature_support = FeatureSupport("fake_entity", fake_type_controller, False) + feature_support = FeatureSupport(fake_type_controller) feature_support._supported_features = number for expected_supported_feature in expected_supported_features: assert await feature_support.is_supported(expected_supported_feature) diff --git a/tests/unit_tests/cx_core/feature_support/media_player_support_test.py b/tests/unit_tests/cx_core/feature_support/media_player_support_test.py index 62630caf..128beaa5 100644 --- a/tests/unit_tests/cx_core/feature_support/media_player_support_test.py +++ b/tests/unit_tests/cx_core/feature_support/media_player_support_test.py @@ -37,7 +37,7 @@ async def test_is_supported( number: int, expected_supported_features: List[int], ): - feature_support = FeatureSupport("fake_entity", fake_type_controller, False) + feature_support = FeatureSupport(fake_type_controller) feature_support._supported_features = number for expected_supported_feature in expected_supported_features: assert await feature_support.is_supported(expected_supported_feature) diff --git a/tests/unit_tests/cx_core/type/cover_controller_test.py b/tests/unit_tests/cx_core/type/cover_controller_test.py index a06a94f7..db8559ab 100644 --- a/tests/unit_tests/cx_core/type/cover_controller_test.py +++ b/tests/unit_tests/cx_core/type/cover_controller_test.py @@ -17,6 +17,7 @@ @pytest.mark.asyncio async def sut_before_init(mocker: MockerFixture) -> CoverController: controller = CoverController() # type: ignore + mocker.patch.object(controller, "get_state", fake_fn(None, async_=True)) mocker.patch.object(TypeController, "init") return controller @@ -25,6 +26,7 @@ async def sut_before_init(mocker: MockerFixture) -> CoverController: @pytest.mark.asyncio async def sut(mocker: MockerFixture) -> CoverController: controller = CoverController() # type: ignore + mocker.patch.object(controller, "get_state", fake_fn(None, async_=True)) mocker.patch.object(Controller, "init") controller.args = {"cover": ENTITY_NAME} await controller.init() diff --git a/tests/unit_tests/cx_core/type/light_controller_test.py b/tests/unit_tests/cx_core/type/light_controller_test.py index 0b09e02b..2d7669eb 100644 --- a/tests/unit_tests/cx_core/type/light_controller_test.py +++ b/tests/unit_tests/cx_core/type/light_controller_test.py @@ -22,6 +22,7 @@ async def sut_before_init(mocker: MockerFixture) -> LightController: controller = LightController() # type: ignore controller.args = {} + mocker.patch.object(controller, "get_state", fake_fn(None, async_=True)) mocker.patch.object(Controller, "init") return controller @@ -30,6 +31,7 @@ async def sut_before_init(mocker: MockerFixture) -> LightController: @pytest.mark.asyncio async def sut(mocker: MockerFixture) -> LightController: controller = LightController() # type: ignore + mocker.patch.object(controller, "get_state", fake_fn(None, async_=True)) mocker.patch.object(Controller, "init") controller.args = {"light": ENTITY_NAME} await controller.init() @@ -122,7 +124,7 @@ async def test_get_attribute( error_expected: bool, ): sut.feature_support._supported_features = supported_features - sut.entity = LightEntity(name=ENTITY_NAME, color_mode=color_mode) + sut.entity = LightEntity(ENTITY_NAME, color_mode=color_mode) with wrap_exetuction(error_expected=error_expected, exception=ValueError): output = await sut.get_attribute(attribute_input) @@ -132,40 +134,34 @@ async def test_get_attribute( @pytest.mark.parametrize( - "attribute_input, smooth_power_on_check, light_state, expected_output, error_expected", + "attribute_input, smooth_power_on_check, expected_output, error_expected", [ - ("xy_color", False, "any", 0, False), - ("brightness", False, "any", 3.0, False), - ("brightness", False, "any", "3.0", False), - ("brightness", False, "any", "3", False), - ("color_temp", False, "any", 1, False), - ("xy_color", False, "any", 0, False), - ("brightness", True, "off", 0, False), - ("brightness", False, "any", "error", True), - ("brightness", False, "any", None, True), - ("color_temp", False, "any", None, True), - ("not_a_valid_attribute", False, "any", None, True), + ("xy_color", False, 0, False), + ("brightness", False, 3.0, False), + ("brightness", False, "3.0", False), + ("brightness", False, "3", False), + ("color_temp", False, 1, False), + ("xy_color", False, 0, False), + ("brightness", True, 0, False), + ("brightness", False, "error", True), + ("brightness", False, None, True), + ("color_temp", False, None, True), + ("not_a_valid_attribute", False, None, True), ], ) @pytest.mark.asyncio async def test_get_value_attribute( sut: LightController, - monkeypatch: MonkeyPatch, + mocker: MockerFixture, attribute_input: str, smooth_power_on_check: bool, - light_state: str, expected_output: Union[int, float, str], error_expected: bool, ): sut.smooth_power_on = True sut.smooth_power_on_check = smooth_power_on_check - async def fake_get_entity_state(entity, attribute=None): - if entity == "light" and attribute is None: - return light_state - return expected_output - - monkeypatch.setattr(sut, "get_entity_state", fake_get_entity_state) + mocker.patch.object(sut, "get_entity_state", fake_fn(expected_output, async_=True)) with wrap_exetuction(error_expected=error_expected, exception=ValueError): output = await sut.get_value_attribute(attribute_input) diff --git a/tests/unit_tests/cx_core/type/media_player_controller_test.py b/tests/unit_tests/cx_core/type/media_player_controller_test.py index 692f9306..c00f2380 100644 --- a/tests/unit_tests/cx_core/type/media_player_controller_test.py +++ b/tests/unit_tests/cx_core/type/media_player_controller_test.py @@ -18,6 +18,7 @@ @pytest.mark.asyncio async def sut(mocker: MockerFixture) -> MediaPlayerController: controller = MediaPlayerController() # type: ignore + mocker.patch.object(controller, "get_state", fake_fn(None, async_=True)) mocker.patch.object(Controller, "init") controller.args = {"media_player": ENTITY_NAME} await controller.init() @@ -186,7 +187,7 @@ async def test_change_source_list( ): called_service_patch = mocker.patch.object(sut, "call_service") - async def fake_get_entity_state(entity, attribute=None): + async def fake_get_entity_state(attribute=None): if active_source is None: return {"attributes": {"source_list": source_list}} else: diff --git a/tests/unit_tests/cx_core/type/switch_controller_test.py b/tests/unit_tests/cx_core/type/switch_controller_test.py index 5ae7a1e4..3301c2e8 100644 --- a/tests/unit_tests/cx_core/type/switch_controller_test.py +++ b/tests/unit_tests/cx_core/type/switch_controller_test.py @@ -3,14 +3,17 @@ from cx_core.type_controller import Entity from pytest_mock.plugin import MockerFixture +from tests.test_utils import fake_fn + ENTITY_NAME = "switch.test" @pytest.fixture @pytest.mark.asyncio -async def sut(): +async def sut(mocker: MockerFixture): c = SwitchController() # type: ignore - c.entity = Entity(ENTITY_NAME) + mocker.patch.object(c, "get_state", fake_fn(None, async_=True)) + c.entity = Entity(name=ENTITY_NAME) return c diff --git a/tests/unit_tests/cx_core/type_controller_test.py b/tests/unit_tests/cx_core/type_controller_test.py index 385edf2e..26c0a30c 100644 --- a/tests/unit_tests/cx_core/type_controller_test.py +++ b/tests/unit_tests/cx_core/type_controller_test.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Type +from typing import Any, Dict, List, Optional, Type, Union import pytest from _pytest.monkeypatch import MonkeyPatch @@ -16,8 +16,13 @@ class MyEntity(Entity): attr_test: str - def __init__(self, name: str, attr_test: str = DEFAULT_ATTR_TEST) -> None: - super().__init__(name) + def __init__( + self, + name: str, + entities: Optional[List[str]] = None, + attr_test: str = DEFAULT_ATTR_TEST, + ) -> None: + super().__init__(name, entities) self.attr_test = attr_test @@ -34,6 +39,7 @@ def _get_entity_type(self) -> Type[MyEntity]: def sut_before_init(mocker: MockerFixture) -> MyTypeController: controller = MyTypeController() # type: ignore controller.args = {ENTITY_ARG: ENTITY_NAME} + mocker.patch.object(controller, "get_state", fake_fn(None, async_=True)) mocker.patch.object(Controller, "init") return controller @@ -75,10 +81,10 @@ async def test_init( @pytest.mark.parametrize( "entity, domains, entities, error_expected", [ - ("light.kitchen", ["light"], [], False), - ("light1.kitchen", ["light"], [], True), - ("media_player.kitchen", ["light"], [], True), - ("media_player.bedroom", ["media_player"], [], False), + ("light.kitchen", ["light"], None, False), + ("light1.kitchen", ["light"], None, True), + ("media_player.kitchen", ["light"], None, True), + ("media_player.bedroom", ["media_player"], None, False), ("group.all_lights", ["light"], ["light.light1", "light.light2"], False), ("group.all_lights", ["light"], ["light1.light1", "light2.light2"], True), ("group.all", ["media_player"], ["media_player.test", "light.test"], True), @@ -88,8 +94,8 @@ async def test_init( ["switch.switch1", "input_boolean.input_boolean1"], False, ), - ("switch.switch1", ["switch", "input_boolean"], [], False), - ("switch.switch1", ["binary_sensor", "input_boolean"], [], True), + ("switch.switch1", ["switch", "input_boolean"], None, False), + ("switch.switch1", ["binary_sensor", "input_boolean"], None, True), ( "group.all", ["switch", "input_boolean"], @@ -99,7 +105,7 @@ async def test_init( ( "{{ to_render }}", ["light"], - [], + None, False, ), ], @@ -114,37 +120,21 @@ async def test_check_domain( error_expected: bool, ): sut.domains = domains - expected_error_message = "" - if error_expected: - if entities == []: - expected_error_message = ( - f"'{entity}' must be from one of the following domains " - f"{domains} (e.g. {domains[0]}.bedroom)" - ) - - else: - expected_error_message = ( - f"All entities from '{entity}' must be from one of the " - f"following domains {domains} (e.g. {domains[0]}.bedroom)" - ) - + my_entity = MyEntity(entity, entities=entities) monkeypatch.setattr(sut, "get_state", fake_fn(to_return=entities, async_=True)) - with wrap_exetuction( - error_expected=error_expected, exception=ValueError - ) as err_info: - await sut.check_domain(entity) - - if err_info is not None: - assert str(err_info.value) == expected_error_message + with wrap_exetuction(error_expected=error_expected, exception=ValueError): + sut._check_domain(my_entity) @pytest.mark.parametrize( - "entity_input, entities, expected_calls", + "entity_input, entities, update_supported_features, expected_calls", [ - ("light.kitchen", ["entity.test"], 1), - ("group.lights", ["entity.test"], 2), - ("group.lights", [], None), + ("entity.test", None, False, 1), + ("entity.test", "entity.test", True, 2), + ("group.lights", ["entity.test"], False, 1), + ("group.lights", ["entity.test"], True, 2), + ("group.lights", [], True, None), ], ) @pytest.mark.asyncio @@ -153,9 +143,11 @@ async def test_get_entity_state( mocker: MockerFixture, monkeypatch: MonkeyPatch, entity_input: str, - entities: List[str], + entities: Union[str, List[str]], + update_supported_features: bool, expected_calls: int, ): + sut.update_supported_features = update_supported_features stub_get_state = mocker.stub() async def fake_get_state(entity, attribute=None): @@ -164,8 +156,9 @@ async def fake_get_state(entity, attribute=None): monkeypatch.setattr(sut, "get_state", fake_get_state) + sut.entity = MyEntity(entity_input) with wrap_exetuction(error_expected=expected_calls is None, exception=ValueError): - await sut.get_entity_state(entity_input, "attribute_test") + await sut.get_entity_state(attribute="attribute_test") if expected_calls is not None: if expected_calls == 1: From 5622f30609f3bffd2286cd9fda95b148fa34f25c Mon Sep 17 00:00:00 2001 From: Xavier Moreno Date: Sat, 3 Jul 2021 19:04:15 +0200 Subject: [PATCH 2/3] =?UTF-8?q?bump:=20version=204.13.0=20=E2=86=92=204.14?= =?UTF-8?q?.0b0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cz.toml | 2 +- apps/controllerx/cx_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.cz.toml b/.cz.toml index b04fb5f7..6e2b5f62 100644 --- a/.cz.toml +++ b/.cz.toml @@ -1,6 +1,6 @@ [tool.commitizen] name = "cz_conventional_commits" -version = "4.13.0" +version = "4.14.0b0" tag_format = "v$major.$minor.$patch$prerelease" version_files = [ "apps/controllerx/cx_version.py", diff --git a/apps/controllerx/cx_version.py b/apps/controllerx/cx_version.py index 6d4f9d37..cbec1f12 100644 --- a/apps/controllerx/cx_version.py +++ b/apps/controllerx/cx_version.py @@ -1 +1 @@ -__version__ = "v4.13.0" +__version__ = "v4.14.0b0" From a535e2c8dc84e31788ad6f77096292b6423038e5 Mon Sep 17 00:00:00 2001 From: Xavier Moreno Date: Sun, 4 Jul 2021 16:56:41 +0200 Subject: [PATCH 3/3] refactor(integ_tests): add entity_id to mocked attributes --- apps/controllerx/cx_core/type_controller.py | 4 ++-- .../action-types/arrow_left_click_test.yaml | 2 -- .../arrow_left_double_click_test.yaml | 2 -- .../arrow_left_quadruple_click_test.yaml | 2 -- .../arrow_left_quintuple_click_test.yaml | 2 -- .../arrow_left_sextuple_click_test.yaml | 2 -- .../arrow_left_triple_click_test.yaml | 2 -- .../action-types/arrow_right_click_test.yaml | 10 ++++---- .../brightness_down_click_test.yaml | 2 -- .../brightness_up_click_test.yaml | 8 +++---- .../integ_tests/action-types/toggle_test.yaml | 8 +++---- .../example_config_hold_test.yaml | 1 - .../example_config_toggle_xycolor_test.yaml | 18 +++++++-------- .../example_config/group_light_test.yaml | 3 +-- .../toggle_called_twice_test.yaml | 9 ++++---- tests/integ_tests/integ_test.py | 20 ++++++++-------- .../merge_mapping/brightness_up_test.yaml | 5 +--- .../merge_mapping/toggle_test.yaml | 9 +++----- .../xy_color_from_controller_test.yaml | 2 -- ...y_color_from_controller_test.yaml.disabled | 3 +-- .../deconz_event_1000_1click_test.yaml | 4 +--- .../deconz_event_1001_1click_test.yaml | 4 +--- .../deconz_event_1002_1click_test.yaml | 4 +--- .../deconz_event_1003_1click_test.yaml | 8 +++---- .../deconz_event_1003_2clicks_test.yaml | 4 +--- ...onz_event_1003_with_1004_2clicks_test.yaml | 4 +--- .../multiple_clicks/toggle_1_click_test.yaml | 4 +--- .../multiple_clicks/toggle_2_clicks_test.yaml | 4 +--- .../multiple_clicks/toggle_3_clicks_test.yaml | 4 +--- .../multiple_clicks/toggle_5_clicks_test.yaml | 7 +++--- .../multiple_clicks/toggle_8_clicks_test.yaml | 23 +++++++++++++++---- .../arrow_hold_test.yaml | 1 - .../toggle_full_brightness_test.yaml | 11 ++++----- .../toggle_full_color_temp_test.yaml | 11 ++++----- .../toggle_min_color_temp_test.yaml | 11 ++++----- .../zb5122/move_to_color_test.yaml | 2 -- 36 files changed, 84 insertions(+), 136 deletions(-) diff --git a/apps/controllerx/cx_core/type_controller.py b/apps/controllerx/cx_core/type_controller.py index 82377070..5f7f52d3 100644 --- a/apps/controllerx/cx_core/type_controller.py +++ b/apps/controllerx/cx_core/type_controller.py @@ -74,8 +74,8 @@ async def _get_entities(self, entity_name: str) -> Optional[List[str]]: f"Entities from `{entity_name}` (entity_id attribute): `{entities}`", level="DEBUG", ) - # When the entity is not a group, this attribute returns the entity name - if isinstance(entities, str): + # If the entity groups other entities, this attribute will be a list + if not isinstance(entities, (list, tuple)): return None if entities is not None and len(entities) == 0: raise ValueError(f"`{entity_name}` does not have any entities registered.") diff --git a/tests/integ_tests/action-types/arrow_left_click_test.yaml b/tests/integ_tests/action-types/arrow_left_click_test.yaml index 477e3b8c..4f111ab1 100644 --- a/tests/integ_tests/action-types/arrow_left_click_test.yaml +++ b/tests/integ_tests/action-types/arrow_left_click_test.yaml @@ -1,5 +1,3 @@ -entity_state_attributes: - supported_features: 191 entity_state: "off" fired_actions: [arrow_left_click] expected_calls: diff --git a/tests/integ_tests/action-types/arrow_left_double_click_test.yaml b/tests/integ_tests/action-types/arrow_left_double_click_test.yaml index 37c31ebc..b2b7f7ee 100644 --- a/tests/integ_tests/action-types/arrow_left_double_click_test.yaml +++ b/tests/integ_tests/action-types/arrow_left_double_click_test.yaml @@ -1,5 +1,3 @@ -entity_state_attributes: - supported_features: 191 entity_state: "off" fired_actions: [arrow_left_click, 0.05, arrow_left_click] expected_calls: diff --git a/tests/integ_tests/action-types/arrow_left_quadruple_click_test.yaml b/tests/integ_tests/action-types/arrow_left_quadruple_click_test.yaml index 5b844141..7e2dd404 100644 --- a/tests/integ_tests/action-types/arrow_left_quadruple_click_test.yaml +++ b/tests/integ_tests/action-types/arrow_left_quadruple_click_test.yaml @@ -1,5 +1,3 @@ -entity_state_attributes: - supported_features: 191 entity_state: "off" fired_actions: [ diff --git a/tests/integ_tests/action-types/arrow_left_quintuple_click_test.yaml b/tests/integ_tests/action-types/arrow_left_quintuple_click_test.yaml index a13ec914..69f80c4f 100644 --- a/tests/integ_tests/action-types/arrow_left_quintuple_click_test.yaml +++ b/tests/integ_tests/action-types/arrow_left_quintuple_click_test.yaml @@ -1,5 +1,3 @@ -entity_state_attributes: - supported_features: 191 entity_state: "off" fired_actions: [ diff --git a/tests/integ_tests/action-types/arrow_left_sextuple_click_test.yaml b/tests/integ_tests/action-types/arrow_left_sextuple_click_test.yaml index 73eb69a9..82e765d8 100644 --- a/tests/integ_tests/action-types/arrow_left_sextuple_click_test.yaml +++ b/tests/integ_tests/action-types/arrow_left_sextuple_click_test.yaml @@ -1,5 +1,3 @@ -entity_state_attributes: - supported_features: 191 entity_state: "off" fired_actions: [ diff --git a/tests/integ_tests/action-types/arrow_left_triple_click_test.yaml b/tests/integ_tests/action-types/arrow_left_triple_click_test.yaml index db920fc7..0d287277 100644 --- a/tests/integ_tests/action-types/arrow_left_triple_click_test.yaml +++ b/tests/integ_tests/action-types/arrow_left_triple_click_test.yaml @@ -1,5 +1,3 @@ -entity_state_attributes: - supported_features: 191 entity_state: "off" fired_actions: [arrow_left_click, 0.05, arrow_left_click, 0.05, arrow_left_click] diff --git a/tests/integ_tests/action-types/arrow_right_click_test.yaml b/tests/integ_tests/action-types/arrow_right_click_test.yaml index f655452c..5d467ad5 100644 --- a/tests/integ_tests/action-types/arrow_right_click_test.yaml +++ b/tests/integ_tests/action-types/arrow_right_click_test.yaml @@ -1,9 +1,7 @@ -entity_state_attributes: - supported_features: 191 entity_state: "off" fired_actions: [arrow_right_click] expected_calls: -- service: my_service - data: - attr1: 42 - attr2: foo + - service: my_service + data: + attr1: 42 + attr2: foo diff --git a/tests/integ_tests/action-types/brightness_down_click_test.yaml b/tests/integ_tests/action-types/brightness_down_click_test.yaml index 8207a174..fb79c525 100644 --- a/tests/integ_tests/action-types/brightness_down_click_test.yaml +++ b/tests/integ_tests/action-types/brightness_down_click_test.yaml @@ -1,5 +1,3 @@ -entity_state_attributes: - supported_features: 191 entity_state: "off" fired_actions: [brightness_down_click] expected_calls: diff --git a/tests/integ_tests/action-types/brightness_up_click_test.yaml b/tests/integ_tests/action-types/brightness_up_click_test.yaml index 7b02386e..175556c5 100644 --- a/tests/integ_tests/action-types/brightness_up_click_test.yaml +++ b/tests/integ_tests/action-types/brightness_up_click_test.yaml @@ -1,8 +1,6 @@ -entity_state_attributes: - supported_features: 191 entity_state: "off" fired_actions: [brightness_up_click] expected_calls: -- service: light/toggle - data: - entity_id: light.bedroom + - service: light/toggle + data: + entity_id: light.bedroom diff --git a/tests/integ_tests/action-types/toggle_test.yaml b/tests/integ_tests/action-types/toggle_test.yaml index 36154d7c..fe55e52f 100644 --- a/tests/integ_tests/action-types/toggle_test.yaml +++ b/tests/integ_tests/action-types/toggle_test.yaml @@ -1,8 +1,6 @@ -entity_state_attributes: - supported_features: 191 entity_state: "off" fired_actions: [toggle] expected_calls: -- service: light/toggle - data: - entity_id: light.bedroom + - service: light/toggle + data: + entity_id: light.bedroom diff --git a/tests/integ_tests/example_config/example_config_hold_test.yaml b/tests/integ_tests/example_config/example_config_hold_test.yaml index d7049ee8..f92f9d3c 100644 --- a/tests/integ_tests/example_config/example_config_hold_test.yaml +++ b/tests/integ_tests/example_config/example_config_hold_test.yaml @@ -1,5 +1,4 @@ entity_state_attributes: - supported_features: 191 brightness: 50 entity_state: "on" fired_actions: [brightness_up_hold, 0.450, brightness_up_release] diff --git a/tests/integ_tests/example_config/example_config_toggle_xycolor_test.yaml b/tests/integ_tests/example_config/example_config_toggle_xycolor_test.yaml index 5ba4be8c..9934ec5a 100644 --- a/tests/integ_tests/example_config/example_config_toggle_xycolor_test.yaml +++ b/tests/integ_tests/example_config/example_config_toggle_xycolor_test.yaml @@ -1,14 +1,12 @@ -entity_state_attributes: - supported_features: 191 entity_state: "off" fired_actions: [toggle, 1.0, toggle_hold] expected_calls: -- service: light/toggle - data: - entity_id: light.livingroom -- service: light/turn_on - data: - entity_id: light.livingroom - xy_color: !!python/tuple [0.323, 0.329] - brightness: 255 + - service: light/toggle + data: + entity_id: light.livingroom + - service: light/turn_on + data: + entity_id: light.livingroom + xy_color: !!python/tuple [0.323, 0.329] + brightness: 255 expected_calls_count: 2 diff --git a/tests/integ_tests/example_config/group_light_test.yaml b/tests/integ_tests/example_config/group_light_test.yaml index bc43e8d2..85045fb6 100644 --- a/tests/integ_tests/example_config/group_light_test.yaml +++ b/tests/integ_tests/example_config/group_light_test.yaml @@ -1,6 +1,5 @@ entity_state_attributes: - supported_features: 191 -entity_entities: ["light.bedroom_1", "light.bedroom_2"] + entity_id: ["light.bedroom_1", "light.bedroom_2"] entity_state: "off" fired_actions: [toggle] expected_calls: diff --git a/tests/integ_tests/example_config/toggle_called_twice_test.yaml b/tests/integ_tests/example_config/toggle_called_twice_test.yaml index 93017b93..ce84866c 100644 --- a/tests/integ_tests/example_config/toggle_called_twice_test.yaml +++ b/tests/integ_tests/example_config/toggle_called_twice_test.yaml @@ -1,10 +1,9 @@ # When the same action is called more than once within 300ms, # then we ignore the rest of the calls that fall between that time. -entity_state_attributes: - supported_features: 191 + entity_state: "off" fired_actions: [toggle, toggle] expected_calls: -- service: light/toggle - data: - entity_id: light.livingroom + - service: light/toggle + data: + entity_id: light.livingroom diff --git a/tests/integ_tests/integ_test.py b/tests/integ_tests/integ_test.py index 019e2c4b..88acd5e2 100644 --- a/tests/integ_tests/integ_test.py +++ b/tests/integ_tests/integ_test.py @@ -9,7 +9,7 @@ from cx_core.type_controller import TypeController from pytest_mock.plugin import MockerFixture -from tests.test_utils import fake_fn, get_controller +from tests.test_utils import get_controller def get_integ_tests(): @@ -29,8 +29,8 @@ def read_config_yaml(file_name): return list(data.values())[0] -def get_fake_entity_states(entity_state, entity_state_attributes): - async def inner(attribute: Optional[str] = None): +def get_fake_get_state(entity_state, entity_state_attributes): + async def inner(entity_name: str, attribute: Optional[str] = None): if attribute is not None and attribute in entity_state_attributes: return entity_state_attributes[attribute] return entity_state @@ -48,13 +48,16 @@ async def test_integ_configs( ): entity_state_attributes = data.get("entity_state_attributes", {}) entity_state = data.get("entity_state", None) - entity_entities = data.get("entity_entities", None) # Used for group entities fired_actions = data.get("fired_actions", []) render_template_response = data.get("render_template_response") extra = data.get("extra") expected_calls = data.get("expected_calls", []) expected_calls_count = data.get("expected_calls_count", len(expected_calls)) + if "supported_features" not in entity_state_attributes: + entity_state_attributes["supported_features"] = 0b1111111111 + if "entity_id" not in entity_state_attributes: + entity_state_attributes["entity_id"] = "my_entity" config = read_config_yaml(config_file) controller = get_controller(config["module"], config["class"]) if controller is None: @@ -67,13 +70,8 @@ async def test_integ_configs( ) if isinstance(controller, TypeController): - fake_entity_states = get_fake_entity_states( - entity_state, entity_state_attributes - ) - mocker.patch.object( - controller, "get_state", fake_fn(entity_entities, async_=True) - ) - mocker.patch.object(controller, "get_entity_state", fake_entity_states) + fake_get_state = get_fake_get_state(entity_state, entity_state_attributes) + mocker.patch.object(controller, "get_state", fake_get_state) call_service_stub = mocker.patch.object(Hass, "call_service") await controller.initialize() diff --git a/tests/integ_tests/merge_mapping/brightness_up_test.yaml b/tests/integ_tests/merge_mapping/brightness_up_test.yaml index e163d316..d2cb9d46 100644 --- a/tests/integ_tests/merge_mapping/brightness_up_test.yaml +++ b/tests/integ_tests/merge_mapping/brightness_up_test.yaml @@ -1,7 +1,4 @@ -entity_state_attributes: - supported_features: 191 entity_state: "on" fired_actions: [step_with_on_off_0_43_5] expected_calls: -- service: my_script - \ No newline at end of file + - service: my_script diff --git a/tests/integ_tests/merge_mapping/toggle_test.yaml b/tests/integ_tests/merge_mapping/toggle_test.yaml index 09b9a609..003092c7 100644 --- a/tests/integ_tests/merge_mapping/toggle_test.yaml +++ b/tests/integ_tests/merge_mapping/toggle_test.yaml @@ -1,9 +1,6 @@ -entity_state_attributes: - supported_features: 191 entity_state: "on" fired_actions: [toggle] expected_calls: -- service: light/toggle - data: - entity_id: light.my_light - \ No newline at end of file + - service: light/toggle + data: + entity_id: light.my_light diff --git a/tests/integ_tests/muller_licht_deconz/xy_color_from_controller_test.yaml b/tests/integ_tests/muller_licht_deconz/xy_color_from_controller_test.yaml index bfa129db..052ba637 100644 --- a/tests/integ_tests/muller_licht_deconz/xy_color_from_controller_test.yaml +++ b/tests/integ_tests/muller_licht_deconz/xy_color_from_controller_test.yaml @@ -1,5 +1,3 @@ -entity_state_attributes: - supported_features: 191 entity_state: "off" fired_actions: [6002] extra: diff --git a/tests/integ_tests/muller_licht_z2m/xy_color_from_controller_test.yaml.disabled b/tests/integ_tests/muller_licht_z2m/xy_color_from_controller_test.yaml.disabled index 5a5ecccd..2c46dd84 100644 --- a/tests/integ_tests/muller_licht_z2m/xy_color_from_controller_test.yaml.disabled +++ b/tests/integ_tests/muller_licht_z2m/xy_color_from_controller_test.yaml.disabled @@ -1,5 +1,4 @@ -entity_state_attributes: - supported_features: 191 + entity_state: "off" fired_actions: ["color_wheel"] extra: diff --git a/tests/integ_tests/multiple_clicks/deconz_event_1000_1click_test.yaml b/tests/integ_tests/multiple_clicks/deconz_event_1000_1click_test.yaml index b5086db5..ad7392fa 100644 --- a/tests/integ_tests/multiple_clicks/deconz_event_1000_1click_test.yaml +++ b/tests/integ_tests/multiple_clicks/deconz_event_1000_1click_test.yaml @@ -1,8 +1,6 @@ -entity_state_attributes: - supported_features: 191 entity_state: "on" fired_actions: [1000] expected_calls: - service: light/toggle data: - entity_id: light.bedroom \ No newline at end of file + entity_id: light.bedroom diff --git a/tests/integ_tests/multiple_clicks/deconz_event_1001_1click_test.yaml b/tests/integ_tests/multiple_clicks/deconz_event_1001_1click_test.yaml index 89b6f02d..61c62db6 100644 --- a/tests/integ_tests/multiple_clicks/deconz_event_1001_1click_test.yaml +++ b/tests/integ_tests/multiple_clicks/deconz_event_1001_1click_test.yaml @@ -1,8 +1,6 @@ -entity_state_attributes: - supported_features: 191 entity_state: "on" fired_actions: [1001] expected_calls: - service: light/toggle data: - entity_id: light.bedroom \ No newline at end of file + entity_id: light.bedroom diff --git a/tests/integ_tests/multiple_clicks/deconz_event_1002_1click_test.yaml b/tests/integ_tests/multiple_clicks/deconz_event_1002_1click_test.yaml index 08ffbf8a..f31c1af1 100644 --- a/tests/integ_tests/multiple_clicks/deconz_event_1002_1click_test.yaml +++ b/tests/integ_tests/multiple_clicks/deconz_event_1002_1click_test.yaml @@ -1,6 +1,4 @@ -entity_state_attributes: - supported_features: 191 entity_state: "on" fired_actions: [1002] expected_calls: - - service: fake_service1002 \ No newline at end of file + - service: fake_service1002 diff --git a/tests/integ_tests/multiple_clicks/deconz_event_1003_1click_test.yaml b/tests/integ_tests/multiple_clicks/deconz_event_1003_1click_test.yaml index 5734d0aa..46a01f09 100644 --- a/tests/integ_tests/multiple_clicks/deconz_event_1003_1click_test.yaml +++ b/tests/integ_tests/multiple_clicks/deconz_event_1003_1click_test.yaml @@ -1,8 +1,6 @@ -entity_state_attributes: - supported_features: 191 entity_state: "on" fired_actions: [1003] expected_calls: -- service: light/toggle - data: - entity_id: light.bedroom \ No newline at end of file + - service: light/toggle + data: + entity_id: light.bedroom diff --git a/tests/integ_tests/multiple_clicks/deconz_event_1003_2clicks_test.yaml b/tests/integ_tests/multiple_clicks/deconz_event_1003_2clicks_test.yaml index 59a15178..f8d13566 100644 --- a/tests/integ_tests/multiple_clicks/deconz_event_1003_2clicks_test.yaml +++ b/tests/integ_tests/multiple_clicks/deconz_event_1003_2clicks_test.yaml @@ -1,6 +1,4 @@ -entity_state_attributes: - supported_features: 191 entity_state: "on" fired_actions: [1003, 0.05, 1003] expected_calls: -- service: fake_service1003 \ No newline at end of file + - service: fake_service1003 diff --git a/tests/integ_tests/multiple_clicks/deconz_event_1003_with_1004_2clicks_test.yaml b/tests/integ_tests/multiple_clicks/deconz_event_1003_with_1004_2clicks_test.yaml index 9b193769..3a7cf0a9 100644 --- a/tests/integ_tests/multiple_clicks/deconz_event_1003_with_1004_2clicks_test.yaml +++ b/tests/integ_tests/multiple_clicks/deconz_event_1003_with_1004_2clicks_test.yaml @@ -1,6 +1,4 @@ -entity_state_attributes: - supported_features: 191 entity_state: "on" fired_actions: [1003, 0.05, 1004, 0.05, 1003] expected_calls: -- service: fake_service1003 \ No newline at end of file + - service: fake_service1003 diff --git a/tests/integ_tests/multiple_clicks/toggle_1_click_test.yaml b/tests/integ_tests/multiple_clicks/toggle_1_click_test.yaml index 296deefd..85f70bc3 100644 --- a/tests/integ_tests/multiple_clicks/toggle_1_click_test.yaml +++ b/tests/integ_tests/multiple_clicks/toggle_1_click_test.yaml @@ -1,6 +1,4 @@ -entity_state_attributes: - supported_features: 191 entity_state: "on" fired_actions: ["toggle"] expected_calls: - - service: fake_service1 \ No newline at end of file + - service: fake_service1 diff --git a/tests/integ_tests/multiple_clicks/toggle_2_clicks_test.yaml b/tests/integ_tests/multiple_clicks/toggle_2_clicks_test.yaml index 6ff8a023..694b72cf 100644 --- a/tests/integ_tests/multiple_clicks/toggle_2_clicks_test.yaml +++ b/tests/integ_tests/multiple_clicks/toggle_2_clicks_test.yaml @@ -1,6 +1,4 @@ -entity_state_attributes: - supported_features: 191 entity_state: "on" fired_actions: ["toggle", 0.05, "toggle"] expected_calls: - - service: fake_service2 \ No newline at end of file + - service: fake_service2 diff --git a/tests/integ_tests/multiple_clicks/toggle_3_clicks_test.yaml b/tests/integ_tests/multiple_clicks/toggle_3_clicks_test.yaml index 870caaa3..61e838f3 100644 --- a/tests/integ_tests/multiple_clicks/toggle_3_clicks_test.yaml +++ b/tests/integ_tests/multiple_clicks/toggle_3_clicks_test.yaml @@ -1,6 +1,4 @@ -entity_state_attributes: - supported_features: 191 entity_state: "on" fired_actions: ["toggle", 0.05, "toggle", 0.05, "toggle"] expected_calls: - - service: fake_service3 \ No newline at end of file + - service: fake_service3 diff --git a/tests/integ_tests/multiple_clicks/toggle_5_clicks_test.yaml b/tests/integ_tests/multiple_clicks/toggle_5_clicks_test.yaml index 0ff3daf2..8fb89995 100644 --- a/tests/integ_tests/multiple_clicks/toggle_5_clicks_test.yaml +++ b/tests/integ_tests/multiple_clicks/toggle_5_clicks_test.yaml @@ -1,6 +1,5 @@ -entity_state_attributes: - supported_features: 191 entity_state: "on" -fired_actions: ["toggle", 0.05, "toggle", 0.05, "toggle", 0.05, "toggle", 0.05, "toggle"] +fired_actions: + ["toggle", 0.05, "toggle", 0.05, "toggle", 0.05, "toggle", 0.05, "toggle"] expected_calls: - - service: fake_service5 \ No newline at end of file + - service: fake_service5 diff --git a/tests/integ_tests/multiple_clicks/toggle_8_clicks_test.yaml b/tests/integ_tests/multiple_clicks/toggle_8_clicks_test.yaml index c7b85698..f90e2c28 100644 --- a/tests/integ_tests/multiple_clicks/toggle_8_clicks_test.yaml +++ b/tests/integ_tests/multiple_clicks/toggle_8_clicks_test.yaml @@ -1,6 +1,21 @@ -entity_state_attributes: - supported_features: 191 entity_state: "on" -fired_actions: ["toggle", 0.05, "toggle", 0.05, "toggle", 0.05, "toggle", 0.05, "toggle", 0.05, "toggle", 0.05, "toggle", 0.05, "toggle"] +fired_actions: + [ + "toggle", + 0.05, + "toggle", + 0.05, + "toggle", + 0.05, + "toggle", + 0.05, + "toggle", + 0.05, + "toggle", + 0.05, + "toggle", + 0.05, + "toggle", + ] expected_calls: - - service: fake_service8 \ No newline at end of file + - service: fake_service8 diff --git a/tests/integ_tests/supported_features_field/arrow_hold_test.yaml b/tests/integ_tests/supported_features_field/arrow_hold_test.yaml index 7b21bbfe..2ad9c022 100644 --- a/tests/integ_tests/supported_features_field/arrow_hold_test.yaml +++ b/tests/integ_tests/supported_features_field/arrow_hold_test.yaml @@ -2,7 +2,6 @@ # from configuration, ControllerX should send color_temp and not xy_color # because light_mode is auto. entity_state_attributes: - supported_features: 0b1111111 # Everything supported from the device color_temp: 200 entity_state: "on" fired_actions: [arrow_right_hold, 0.450, arrow_right_release] diff --git a/tests/integ_tests/toggle_full_and_min/toggle_full_brightness_test.yaml b/tests/integ_tests/toggle_full_and_min/toggle_full_brightness_test.yaml index 49ffca41..24186525 100644 --- a/tests/integ_tests/toggle_full_and_min/toggle_full_brightness_test.yaml +++ b/tests/integ_tests/toggle_full_and_min/toggle_full_brightness_test.yaml @@ -1,10 +1,7 @@ -entity_state_attributes: - supported_features: 191 entity_state: "on" fired_actions: [toggle] expected_calls: -- service: light/toggle - data: - entity_id: light.my_light - brightness: 255 - \ No newline at end of file + - service: light/toggle + data: + entity_id: light.my_light + brightness: 255 diff --git a/tests/integ_tests/toggle_full_and_min/toggle_full_color_temp_test.yaml b/tests/integ_tests/toggle_full_and_min/toggle_full_color_temp_test.yaml index fe289dbd..0a4fec4f 100644 --- a/tests/integ_tests/toggle_full_and_min/toggle_full_color_temp_test.yaml +++ b/tests/integ_tests/toggle_full_and_min/toggle_full_color_temp_test.yaml @@ -1,10 +1,7 @@ -entity_state_attributes: - supported_features: 191 entity_state: "on" fired_actions: [brightness_up_click] expected_calls: -- service: light/toggle - data: - entity_id: light.my_light - color_temp: 300 - \ No newline at end of file + - service: light/toggle + data: + entity_id: light.my_light + color_temp: 300 diff --git a/tests/integ_tests/toggle_full_and_min/toggle_min_color_temp_test.yaml b/tests/integ_tests/toggle_full_and_min/toggle_min_color_temp_test.yaml index 9459d9ad..77680986 100644 --- a/tests/integ_tests/toggle_full_and_min/toggle_min_color_temp_test.yaml +++ b/tests/integ_tests/toggle_full_and_min/toggle_min_color_temp_test.yaml @@ -1,10 +1,7 @@ -entity_state_attributes: - supported_features: 191 entity_state: "on" fired_actions: [brightness_down_click] expected_calls: -- service: light/toggle - data: - entity_id: light.my_light - color_temp: 100 - \ No newline at end of file + - service: light/toggle + data: + entity_id: light.my_light + color_temp: 100 diff --git a/tests/integ_tests/zb5122/move_to_color_test.yaml b/tests/integ_tests/zb5122/move_to_color_test.yaml index 11003a77..4427dc1b 100644 --- a/tests/integ_tests/zb5122/move_to_color_test.yaml +++ b/tests/integ_tests/zb5122/move_to_color_test.yaml @@ -1,5 +1,3 @@ -entity_state_attributes: - supported_features: 191 entity_state: "off" fired_actions: ["move_to_color_temp"] extra: