mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Refactor Twinkly tests (#133725)
This commit is contained in:
parent
31c6443a9b
commit
7be3cad1db
@ -17,7 +17,7 @@
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"abort": {
|
||||
"device_exists": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,120 +1,13 @@
|
||||
"""Constants and mock for the twinkly component tests."""
|
||||
|
||||
from aiohttp.client_exceptions import ClientConnectionError
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from homeassistant.components.twinkly.const import DEV_NAME
|
||||
|
||||
TEST_HOST = "test.twinkly.com"
|
||||
TEST_ID = "twinkly_test_device_id"
|
||||
TEST_UID = "4c8fccf5-e08a-4173-92d5-49bf479252a2"
|
||||
TEST_MAC = "aa:bb:cc:dd:ee:ff"
|
||||
TEST_NAME = "twinkly_test_device_name"
|
||||
TEST_NAME_ORIGINAL = "twinkly_test_original_device_name" # the original (deprecated) name stored in the conf
|
||||
TEST_MODEL = "twinkly_test_device_model"
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
class ClientMock:
|
||||
"""A mock of the ttls.client.Twinkly."""
|
||||
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
|
||||
"""Fixture for setting up the component."""
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Create a mocked client."""
|
||||
self.is_offline = False
|
||||
self.state = True
|
||||
self.brightness = {"mode": "enabled", "value": 10}
|
||||
self.color = None
|
||||
self.movies = [{"id": 1, "name": "Rainbow"}, {"id": 2, "name": "Flare"}]
|
||||
self.current_movie = {}
|
||||
self.default_mode = "movie"
|
||||
self.mode = None
|
||||
self.version = "2.8.10"
|
||||
|
||||
self.id = TEST_UID
|
||||
self.device_info = {
|
||||
"uuid": self.id,
|
||||
"device_name": TEST_NAME,
|
||||
"mac": TEST_MAC,
|
||||
"product_code": TEST_MODEL,
|
||||
}
|
||||
|
||||
@property
|
||||
def host(self) -> str:
|
||||
"""Get the mocked host."""
|
||||
return TEST_HOST
|
||||
|
||||
async def get_details(self):
|
||||
"""Get the mocked device info."""
|
||||
if self.is_offline:
|
||||
raise ClientConnectionError
|
||||
return self.device_info
|
||||
|
||||
async def is_on(self) -> bool:
|
||||
"""Get the mocked on/off state."""
|
||||
if self.is_offline:
|
||||
raise ClientConnectionError
|
||||
return self.state
|
||||
|
||||
async def turn_on(self) -> None:
|
||||
"""Set the mocked on state."""
|
||||
if self.is_offline:
|
||||
raise ClientConnectionError
|
||||
self.state = True
|
||||
self.mode = self.default_mode
|
||||
|
||||
async def turn_off(self) -> None:
|
||||
"""Set the mocked off state."""
|
||||
if self.is_offline:
|
||||
raise ClientConnectionError
|
||||
self.state = False
|
||||
|
||||
async def get_brightness(self) -> int:
|
||||
"""Get the mocked brightness."""
|
||||
if self.is_offline:
|
||||
raise ClientConnectionError
|
||||
return self.brightness
|
||||
|
||||
async def set_brightness(self, brightness: int) -> None:
|
||||
"""Set the mocked brightness."""
|
||||
if self.is_offline:
|
||||
raise ClientConnectionError
|
||||
self.brightness = {"mode": "enabled", "value": brightness}
|
||||
|
||||
def change_name(self, new_name: str) -> None:
|
||||
"""Change the name of this virtual device."""
|
||||
self.device_info[DEV_NAME] = new_name
|
||||
|
||||
async def set_static_colour(self, colour) -> None:
|
||||
"""Set static color."""
|
||||
self.color = colour
|
||||
self.default_mode = "color"
|
||||
|
||||
async def set_cycle_colours(self, colour) -> None:
|
||||
"""Set static color."""
|
||||
self.color = colour
|
||||
self.default_mode = "movie"
|
||||
|
||||
async def interview(self) -> None:
|
||||
"""Interview."""
|
||||
|
||||
async def get_saved_movies(self) -> dict:
|
||||
"""Get saved movies."""
|
||||
return self.movies
|
||||
|
||||
async def get_current_movie(self) -> dict:
|
||||
"""Get current movie."""
|
||||
return self.current_movie
|
||||
|
||||
async def set_current_movie(self, movie_id: int) -> dict:
|
||||
"""Set current movie."""
|
||||
self.current_movie = {"id": movie_id}
|
||||
|
||||
async def set_mode(self, mode: str) -> None:
|
||||
"""Set mode."""
|
||||
if mode == "off":
|
||||
await self.turn_off()
|
||||
else:
|
||||
await self.turn_on()
|
||||
self.mode = mode
|
||||
|
||||
async def get_firmware_version(self) -> dict:
|
||||
"""Get firmware version."""
|
||||
return {"version": self.version}
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
@ -1,55 +1,74 @@
|
||||
"""Configure tests for the Twinkly integration."""
|
||||
|
||||
from collections.abc import Awaitable, Callable, Coroutine
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.components.twinkly import DOMAIN
|
||||
from homeassistant.const import CONF_HOST, CONF_ID, CONF_MODEL, CONF_NAME
|
||||
|
||||
from . import TEST_MODEL, TEST_NAME, TEST_UID, ClientMock
|
||||
from .const import TEST_MAC, TEST_MODEL, TEST_NAME
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
type ComponentSetup = Callable[[], Awaitable[ClientMock]]
|
||||
|
||||
DOMAIN = "twinkly"
|
||||
TITLE = "Twinkly"
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
load_json_array_fixture,
|
||||
load_json_object_fixture,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(name="config_entry")
|
||||
@pytest.fixture
|
||||
def mock_config_entry() -> MockConfigEntry:
|
||||
"""Create Twinkly entry in Home Assistant."""
|
||||
client = ClientMock()
|
||||
return MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title=TITLE,
|
||||
unique_id=TEST_UID,
|
||||
entry_id=TEST_UID,
|
||||
title="Twinkly",
|
||||
unique_id=TEST_MAC,
|
||||
data={
|
||||
"host": client.host,
|
||||
"id": client.id,
|
||||
"name": TEST_NAME,
|
||||
"model": TEST_MODEL,
|
||||
"device_name": TEST_NAME,
|
||||
CONF_HOST: "192.168.0.123",
|
||||
CONF_ID: "497dcba3-ecbf-4587-a2dd-5eb0665e6880",
|
||||
CONF_NAME: TEST_NAME,
|
||||
CONF_MODEL: TEST_MODEL,
|
||||
},
|
||||
entry_id="01JFMME2P6RA38V5AMPCJ2JYYV",
|
||||
minor_version=2,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(name="setup_integration")
|
||||
async def mock_setup_integration(
|
||||
hass: HomeAssistant, config_entry: MockConfigEntry
|
||||
) -> Callable[[], Coroutine[Any, Any, ClientMock]]:
|
||||
"""Fixture for setting up the component."""
|
||||
config_entry.add_to_hass(hass)
|
||||
@pytest.fixture
|
||||
def mock_twinkly_client() -> Generator[AsyncMock]:
|
||||
"""Mock the Twinkly client."""
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.twinkly.Twinkly",
|
||||
autospec=True,
|
||||
) as mock_client,
|
||||
patch(
|
||||
"homeassistant.components.twinkly.config_flow.Twinkly",
|
||||
new=mock_client,
|
||||
),
|
||||
):
|
||||
client = mock_client.return_value
|
||||
client.get_details.return_value = load_json_object_fixture(
|
||||
"get_details.json", DOMAIN
|
||||
)
|
||||
client.get_firmware_version.return_value = load_json_object_fixture(
|
||||
"get_firmware_version.json", DOMAIN
|
||||
)
|
||||
client.get_saved_movies.return_value = load_json_array_fixture(
|
||||
"get_saved_movies.json", DOMAIN
|
||||
)
|
||||
client.get_current_movie.return_value = load_json_object_fixture(
|
||||
"get_current_movie.json", DOMAIN
|
||||
)
|
||||
client.is_on.return_value = True
|
||||
client.get_brightness.return_value = {"mode": "enabled", "value": 10}
|
||||
client.host = "192.168.0.123"
|
||||
yield client
|
||||
|
||||
async def func() -> ClientMock:
|
||||
mock = ClientMock()
|
||||
with patch("homeassistant.components.twinkly.Twinkly", return_value=mock):
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
return mock
|
||||
|
||||
return func
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[None]:
|
||||
"""Mock setting up a config entry."""
|
||||
with patch("homeassistant.components.twinkly.async_setup_entry", return_value=True):
|
||||
yield
|
||||
|
5
tests/components/twinkly/const.py
Normal file
5
tests/components/twinkly/const.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""Constants for the Twinkly tests."""
|
||||
|
||||
TEST_MAC = "00:2d:13:3b:aa:bb"
|
||||
TEST_NAME = "Tree 1"
|
||||
TEST_MODEL = "TW2016"
|
3
tests/components/twinkly/fixtures/get_current_movie.json
Normal file
3
tests/components/twinkly/fixtures/get_current_movie.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"id": 1
|
||||
}
|
23
tests/components/twinkly/fixtures/get_details.json
Normal file
23
tests/components/twinkly/fixtures/get_details.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"product_name": "Twinkly",
|
||||
"product_version": "1",
|
||||
"hardware_version": "1",
|
||||
"flash_size": 4,
|
||||
"led_type": 1,
|
||||
"led_version": "1",
|
||||
"product_code": "TW2016",
|
||||
"device_name": "Tree 1",
|
||||
"uptime": "4087441",
|
||||
"rssi": -78,
|
||||
"hw_id": "002d133b",
|
||||
"mac": "00:2d:13:3b:aa:bb",
|
||||
"uuid": "00000000-0000-0000-0000-000000000000",
|
||||
"max_supported_led": 100,
|
||||
"base_leds_number": 100,
|
||||
"number_of_led": 100,
|
||||
"led_profile": "RGB",
|
||||
"frame_rate": 14,
|
||||
"movie_capacity": 708,
|
||||
"copyright": "LEDWORKS 2017",
|
||||
"code": 1000
|
||||
}
|
@ -0,0 +1 @@
|
||||
{ "version": "2.7.2" }
|
4
tests/components/twinkly/fixtures/get_saved_movies.json
Normal file
4
tests/components/twinkly/fixtures/get_saved_movies.json
Normal file
@ -0,0 +1,4 @@
|
||||
[
|
||||
{ "id": 1, "name": "Rainbow" },
|
||||
{ "id": 2, "name": "Flare" }
|
||||
]
|
@ -3,35 +3,64 @@
|
||||
dict({
|
||||
'attributes': dict({
|
||||
'brightness': 26,
|
||||
'color_mode': 'brightness',
|
||||
'color_mode': 'rgb',
|
||||
'effect': None,
|
||||
'effect_list': list([
|
||||
]),
|
||||
'friendly_name': 'twinkly_test_device_name',
|
||||
'friendly_name': 'Tree 1',
|
||||
'hs_color': list([
|
||||
0.0,
|
||||
0.0,
|
||||
]),
|
||||
'rgb_color': list([
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
]),
|
||||
'supported_color_modes': list([
|
||||
'brightness',
|
||||
'rgb',
|
||||
]),
|
||||
'supported_features': 4,
|
||||
'xy_color': list([
|
||||
0.323,
|
||||
0.329,
|
||||
]),
|
||||
}),
|
||||
'device_info': dict({
|
||||
'device_name': 'twinkly_test_device_name',
|
||||
'base_leds_number': 100,
|
||||
'code': 1000,
|
||||
'copyright': 'LEDWORKS 2017',
|
||||
'device_name': 'Tree 1',
|
||||
'flash_size': 4,
|
||||
'frame_rate': 14,
|
||||
'hardware_version': '1',
|
||||
'hw_id': '002d133b',
|
||||
'led_profile': 'RGB',
|
||||
'led_type': 1,
|
||||
'led_version': '1',
|
||||
'mac': '**REDACTED**',
|
||||
'product_code': 'twinkly_test_device_model',
|
||||
'uuid': '4c8fccf5-e08a-4173-92d5-49bf479252a2',
|
||||
'max_supported_led': 100,
|
||||
'movie_capacity': 708,
|
||||
'number_of_led': 100,
|
||||
'product_code': 'TW2016',
|
||||
'product_name': 'Twinkly',
|
||||
'product_version': '1',
|
||||
'rssi': -78,
|
||||
'uptime': '4087441',
|
||||
'uuid': '00000000-0000-0000-0000-000000000000',
|
||||
}),
|
||||
'entry': dict({
|
||||
'data': dict({
|
||||
'device_name': 'twinkly_test_device_name',
|
||||
'host': '**REDACTED**',
|
||||
'id': '4c8fccf5-e08a-4173-92d5-49bf479252a2',
|
||||
'model': 'twinkly_test_device_model',
|
||||
'name': 'twinkly_test_device_name',
|
||||
'id': '497dcba3-ecbf-4587-a2dd-5eb0665e6880',
|
||||
'model': 'TW2016',
|
||||
'name': 'Tree 1',
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'discovery_keys': dict({
|
||||
}),
|
||||
'domain': 'twinkly',
|
||||
'entry_id': '4c8fccf5-e08a-4173-92d5-49bf479252a2',
|
||||
'entry_id': '01JFMME2P6RA38V5AMPCJ2JYYV',
|
||||
'minor_version': 2,
|
||||
'options': dict({
|
||||
}),
|
||||
@ -39,9 +68,9 @@
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'title': 'Twinkly',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff',
|
||||
'unique_id': '00:2d:13:3b:aa:bb',
|
||||
'version': 1,
|
||||
}),
|
||||
'sw_version': '2.8.10',
|
||||
'sw_version': '2.7.2',
|
||||
})
|
||||
# ---
|
||||
|
75
tests/components/twinkly/snapshots/test_light.ambr
Normal file
75
tests/components/twinkly/snapshots/test_light.ambr
Normal file
@ -0,0 +1,75 @@
|
||||
# serializer version: 1
|
||||
# name: test_entities[light.tree_1-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'effect_list': list([
|
||||
]),
|
||||
'supported_color_modes': list([
|
||||
<ColorMode.RGB: 'rgb'>,
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'light',
|
||||
'entity_category': None,
|
||||
'entity_id': 'light.tree_1',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'twinkly',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': <LightEntityFeature: 4>,
|
||||
'translation_key': 'light',
|
||||
'unique_id': '00:2d:13:3b:aa:bb',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_entities[light.tree_1-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'brightness': 26,
|
||||
'color_mode': <ColorMode.RGB: 'rgb'>,
|
||||
'effect': None,
|
||||
'effect_list': list([
|
||||
]),
|
||||
'friendly_name': 'Tree 1',
|
||||
'hs_color': tuple(
|
||||
0.0,
|
||||
0.0,
|
||||
),
|
||||
'rgb_color': tuple(
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
),
|
||||
'supported_color_modes': list([
|
||||
<ColorMode.RGB: 'rgb'>,
|
||||
]),
|
||||
'supported_features': <LightEntityFeature: 4>,
|
||||
'xy_color': tuple(
|
||||
0.323,
|
||||
0.329,
|
||||
),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'light.tree_1',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
@ -1,196 +1,170 @@
|
||||
"""Tests for the config_flow of the twinly component."""
|
||||
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import dhcp
|
||||
from homeassistant.components.twinkly.const import DOMAIN as TWINKLY_DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_USER
|
||||
from homeassistant.components.twinkly.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_DHCP, SOURCE_USER
|
||||
from homeassistant.const import CONF_HOST, CONF_ID, CONF_MODEL, CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from . import TEST_MODEL, TEST_NAME, ClientMock
|
||||
from .const import TEST_MAC, TEST_MODEL, TEST_NAME
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_invalid_host(hass: HomeAssistant) -> None:
|
||||
"""Test the failure when invalid host provided."""
|
||||
client = ClientMock()
|
||||
client.is_offline = True
|
||||
with patch(
|
||||
"homeassistant.components.twinkly.config_flow.Twinkly", return_value=client
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
TWINKLY_DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {}
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "dummy"},
|
||||
)
|
||||
@pytest.mark.usefixtures("mock_twinkly_client", "mock_setup_entry")
|
||||
async def test_full_flow(hass: HomeAssistant) -> None:
|
||||
"""Test the full flow."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "192.168.0.123"},
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == TEST_NAME
|
||||
assert result["data"] == {
|
||||
CONF_HOST: "192.168.0.123",
|
||||
CONF_ID: "00000000-0000-0000-0000-000000000000",
|
||||
CONF_NAME: TEST_NAME,
|
||||
CONF_MODEL: TEST_MODEL,
|
||||
}
|
||||
assert result["result"].unique_id == TEST_MAC
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_setup_entry")
|
||||
async def test_exceptions(hass: HomeAssistant, mock_twinkly_client: AsyncMock) -> None:
|
||||
"""Test the failure when raising exceptions."""
|
||||
mock_twinkly_client.get_details.side_effect = TimeoutError
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "192.168.0.123"},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {CONF_HOST: "cannot_connect"}
|
||||
|
||||
mock_twinkly_client.get_details.side_effect = None
|
||||
|
||||
async def test_success_flow(hass: HomeAssistant) -> None:
|
||||
"""Test that an entity is created when the flow completes."""
|
||||
client = ClientMock()
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.twinkly.config_flow.Twinkly", return_value=client
|
||||
),
|
||||
patch("homeassistant.components.twinkly.async_setup_entry", return_value=True),
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
TWINKLY_DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "dummy"},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == TEST_NAME
|
||||
assert result["data"] == {
|
||||
CONF_HOST: "dummy",
|
||||
CONF_ID: client.id,
|
||||
CONF_NAME: TEST_NAME,
|
||||
CONF_MODEL: TEST_MODEL,
|
||||
}
|
||||
|
||||
|
||||
async def test_dhcp_can_confirm(hass: HomeAssistant) -> None:
|
||||
"""Test DHCP discovery flow can confirm right away."""
|
||||
client = ClientMock()
|
||||
with patch(
|
||||
"homeassistant.components.twinkly.config_flow.Twinkly", return_value=client
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
TWINKLY_DOMAIN,
|
||||
context={"source": config_entries.SOURCE_DHCP},
|
||||
data=dhcp.DhcpServiceInfo(
|
||||
hostname="Twinkly_XYZ",
|
||||
ip="1.2.3.4",
|
||||
macaddress="aabbccddeeff",
|
||||
),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "discovery_confirm"
|
||||
|
||||
|
||||
async def test_dhcp_success(hass: HomeAssistant) -> None:
|
||||
"""Test DHCP discovery flow success."""
|
||||
client = ClientMock()
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.twinkly.config_flow.Twinkly", return_value=client
|
||||
),
|
||||
patch("homeassistant.components.twinkly.async_setup_entry", return_value=True),
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
TWINKLY_DOMAIN,
|
||||
context={"source": config_entries.SOURCE_DHCP},
|
||||
data=dhcp.DhcpServiceInfo(
|
||||
hostname="Twinkly_XYZ",
|
||||
ip="1.2.3.4",
|
||||
macaddress="aabbccddeeff",
|
||||
),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "discovery_confirm"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == TEST_NAME
|
||||
assert result["data"] == {
|
||||
CONF_HOST: "1.2.3.4",
|
||||
CONF_ID: client.id,
|
||||
CONF_NAME: TEST_NAME,
|
||||
CONF_MODEL: TEST_MODEL,
|
||||
}
|
||||
|
||||
|
||||
async def test_dhcp_already_exists(hass: HomeAssistant) -> None:
|
||||
"""Test DHCP discovery flow that fails to connect."""
|
||||
client = ClientMock()
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=TWINKLY_DOMAIN,
|
||||
data={
|
||||
CONF_HOST: "1.2.3.4",
|
||||
CONF_ID: client.id,
|
||||
CONF_NAME: TEST_NAME,
|
||||
CONF_MODEL: TEST_MODEL,
|
||||
},
|
||||
unique_id=client.id,
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "192.168.0.123"},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.twinkly.config_flow.Twinkly", return_value=client
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
TWINKLY_DOMAIN,
|
||||
context={"source": config_entries.SOURCE_DHCP},
|
||||
data=dhcp.DhcpServiceInfo(
|
||||
hostname="Twinkly_XYZ",
|
||||
ip="1.2.3.4",
|
||||
macaddress="aabbccddeeff",
|
||||
),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@pytest.mark.usefixtures("mock_twinkly_client", "mock_setup_entry")
|
||||
async def test_already_configured(
|
||||
hass: HomeAssistant, mock_config_entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test the device is already configured."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_HOST: "192.168.0.123"}
|
||||
)
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_twinkly_client", "mock_setup_entry")
|
||||
async def test_dhcp_full_flow(hass: HomeAssistant) -> None:
|
||||
"""Test DHCP discovery flow can confirm right away."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_DHCP},
|
||||
data=dhcp.DhcpServiceInfo(
|
||||
hostname="Twinkly_XYZ",
|
||||
ip="1.2.3.4",
|
||||
macaddress="002d133baabb",
|
||||
),
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "discovery_confirm"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == TEST_NAME
|
||||
assert result["data"] == {
|
||||
CONF_HOST: "1.2.3.4",
|
||||
CONF_ID: "00000000-0000-0000-0000-000000000000",
|
||||
CONF_NAME: TEST_NAME,
|
||||
CONF_MODEL: TEST_MODEL,
|
||||
}
|
||||
assert result["result"].unique_id == TEST_MAC
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_twinkly_client")
|
||||
async def test_dhcp_already_configured(
|
||||
hass: HomeAssistant, mock_config_entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test DHCP discovery flow aborts if entry already setup."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_DHCP},
|
||||
data=dhcp.DhcpServiceInfo(
|
||||
hostname="Twinkly_XYZ",
|
||||
ip="1.2.3.4",
|
||||
macaddress="002d133baabb",
|
||||
),
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
assert mock_config_entry.data[CONF_HOST] == "1.2.3.4"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_twinkly_client", "mock_setup_entry")
|
||||
async def test_user_flow_works_discovery(hass: HomeAssistant) -> None:
|
||||
"""Test user flow can continue after discovery happened."""
|
||||
client = ClientMock()
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.twinkly.config_flow.Twinkly", return_value=client
|
||||
await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_DHCP},
|
||||
data=dhcp.DhcpServiceInfo(
|
||||
hostname="Twinkly_XYZ",
|
||||
ip="1.2.3.4",
|
||||
macaddress="002d133baabb",
|
||||
),
|
||||
patch("homeassistant.components.twinkly.async_setup_entry", return_value=True),
|
||||
):
|
||||
await hass.config_entries.flow.async_init(
|
||||
TWINKLY_DOMAIN,
|
||||
context={"source": config_entries.SOURCE_DHCP},
|
||||
data=dhcp.DhcpServiceInfo(
|
||||
hostname="Twinkly_XYZ",
|
||||
ip="1.2.3.4",
|
||||
macaddress="aabbccddeeff",
|
||||
),
|
||||
)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
TWINKLY_DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
)
|
||||
assert len(hass.config_entries.flow.async_progress(TWINKLY_DOMAIN)) == 2
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
)
|
||||
assert len(hass.config_entries.flow.async_progress(DOMAIN)) == 2
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "10.0.0.131"},
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "10.0.0.131"},
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
|
||||
# Verify the discovery flow was aborted
|
||||
assert not hass.config_entries.flow.async_progress(TWINKLY_DOMAIN)
|
||||
# Verify the discovery flow was aborted
|
||||
assert not hass.config_entries.flow.async_progress(DOMAIN)
|
||||
|
@ -1,32 +1,28 @@
|
||||
"""Tests for the diagnostics of the twinkly component."""
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
from syrupy.filters import props
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import ClientMock
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.components.diagnostics import get_diagnostics_for_config_entry
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
type ComponentSetup = Callable[[], Awaitable[ClientMock]]
|
||||
|
||||
DOMAIN = "twinkly"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_twinkly_client")
|
||||
async def test_diagnostics(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
setup_integration: ComponentSetup,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test diagnostics."""
|
||||
await setup_integration()
|
||||
entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert await get_diagnostics_for_config_entry(hass, hass_client, entry) == snapshot(
|
||||
exclude=props("created_at", "modified_at")
|
||||
)
|
||||
assert await get_diagnostics_for_config_entry(
|
||||
hass, hass_client, mock_config_entry
|
||||
) == snapshot(exclude=props("created_at", "modified_at"))
|
||||
|
@ -1,7 +1,9 @@
|
||||
"""Tests of the initialization of the twinkly integration."""
|
||||
|
||||
from unittest.mock import patch
|
||||
from uuid import uuid4
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from aiohttp import ClientConnectionError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
|
||||
from homeassistant.components.twinkly.const import DOMAIN
|
||||
@ -10,82 +12,55 @@ from homeassistant.const import CONF_HOST, CONF_ID, CONF_MODEL, CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from . import TEST_HOST, TEST_MAC, TEST_MODEL, TEST_NAME_ORIGINAL, ClientMock
|
||||
from . import setup_integration
|
||||
from .const import TEST_MAC, TEST_MODEL
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_load_unload_entry(hass: HomeAssistant) -> None:
|
||||
"""Validate that setup entry also configure the client."""
|
||||
client = ClientMock()
|
||||
@pytest.mark.usefixtures("mock_twinkly_client")
|
||||
async def test_load_unload_entry(
|
||||
hass: HomeAssistant, mock_config_entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test the load/unload of the config entry."""
|
||||
|
||||
device_id = str(uuid4())
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_ID: device_id,
|
||||
CONF_NAME: TEST_NAME_ORIGINAL,
|
||||
CONF_MODEL: TEST_MODEL,
|
||||
},
|
||||
entry_id=device_id,
|
||||
unique_id=TEST_MAC,
|
||||
minor_version=2,
|
||||
)
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
with patch("homeassistant.components.twinkly.Twinkly", return_value=client):
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.config_entries.async_unload(mock_config_entry.entry_id)
|
||||
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||
|
||||
assert config_entry.state is ConfigEntryState.NOT_LOADED
|
||||
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
|
||||
|
||||
|
||||
async def test_config_entry_not_ready(hass: HomeAssistant) -> None:
|
||||
async def test_config_entry_not_ready(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_twinkly_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Validate that config entry is retried."""
|
||||
client = ClientMock()
|
||||
client.is_offline = True
|
||||
mock_twinkly_client.get_details.side_effect = ClientConnectionError
|
||||
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_ID: id,
|
||||
CONF_NAME: TEST_NAME_ORIGINAL,
|
||||
CONF_MODEL: TEST_MODEL,
|
||||
},
|
||||
minor_version=2,
|
||||
unique_id=TEST_MAC,
|
||||
)
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
with patch("homeassistant.components.twinkly.Twinkly", return_value=client):
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
|
||||
assert config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_twinkly_client")
|
||||
async def test_mac_migration(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Validate that the unique_id is migrated to the MAC address."""
|
||||
client = ClientMock()
|
||||
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
minor_version=1,
|
||||
unique_id="unique_id",
|
||||
data={
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_HOST: "192.168.0.123",
|
||||
CONF_ID: id,
|
||||
CONF_NAME: TEST_NAME_ORIGINAL,
|
||||
CONF_NAME: "Tree 1",
|
||||
CONF_MODEL: TEST_MODEL,
|
||||
},
|
||||
)
|
||||
@ -100,8 +75,7 @@ async def test_mac_migration(
|
||||
identifiers={(DOMAIN, config_entry.unique_id)},
|
||||
)
|
||||
|
||||
with patch("homeassistant.components.twinkly.Twinkly", return_value=client):
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
|
@ -3,290 +3,287 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS, LightEntityFeature
|
||||
from homeassistant.components.twinkly.const import DOMAIN as TWINKLY_DOMAIN
|
||||
from homeassistant.const import CONF_HOST, CONF_ID, CONF_MODEL, CONF_NAME
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_EFFECT,
|
||||
ATTR_RGB_COLOR,
|
||||
ATTR_RGBW_COLOR,
|
||||
DOMAIN as LIGHT_DOMAIN,
|
||||
LightEntityFeature,
|
||||
)
|
||||
from homeassistant.components.twinkly import DOMAIN
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
SERVICE_TURN_OFF,
|
||||
SERVICE_TURN_ON,
|
||||
STATE_OFF,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
from homeassistant.helpers.entity_registry import RegistryEntry
|
||||
|
||||
from . import TEST_MAC, TEST_MODEL, TEST_NAME, TEST_NAME_ORIGINAL, ClientMock
|
||||
from . import setup_integration
|
||||
from .const import TEST_MAC
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
|
||||
|
||||
|
||||
async def test_initial_state(hass: HomeAssistant) -> None:
|
||||
"""Validate that entity and device states are updated on startup."""
|
||||
entity, device, _, _ = await _create_entries(hass)
|
||||
@pytest.mark.usefixtures("mock_twinkly_client")
|
||||
async def test_entities(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test the created entities."""
|
||||
with patch("homeassistant.components.twinkly.PLATFORMS", [Platform.LIGHT]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
|
||||
# Basic state properties
|
||||
assert state.name == TEST_NAME
|
||||
assert state.state == "on"
|
||||
assert state.attributes[ATTR_BRIGHTNESS] == 26
|
||||
assert state.attributes["friendly_name"] == TEST_NAME
|
||||
|
||||
assert device.name == TEST_NAME
|
||||
assert device.model == TEST_MODEL
|
||||
assert device.manufacturer == "LEDWORKS"
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
|
||||
async def test_turn_on_off(hass: HomeAssistant) -> None:
|
||||
async def test_turn_on_off(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_twinkly_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test support of the light.turn_on service."""
|
||||
client = ClientMock()
|
||||
client.state = False
|
||||
client.brightness = {"mode": "enabled", "value": 20}
|
||||
entity, _, _, _ = await _create_entries(hass, client)
|
||||
mock_twinkly_client.is_on.return_value = False
|
||||
|
||||
assert hass.states.get(entity.entity_id).state == "off"
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert hass.states.get("light.tree_1").state == STATE_OFF
|
||||
|
||||
await hass.services.async_call(
|
||||
"light", "turn_on", service_data={"entity_id": entity.entity_id}, blocking=True
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
service_data={ATTR_ENTITY_ID: "light.tree_1"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
|
||||
assert state.state == "on"
|
||||
assert state.attributes[ATTR_BRIGHTNESS] == 51
|
||||
mock_twinkly_client.turn_on.assert_called_once_with()
|
||||
|
||||
|
||||
async def test_turn_on_with_brightness(hass: HomeAssistant) -> None:
|
||||
async def test_turn_on_with_brightness(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_twinkly_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test support of the light.turn_on service with a brightness parameter."""
|
||||
client = ClientMock()
|
||||
client.state = False
|
||||
client.brightness = {"mode": "enabled", "value": 20}
|
||||
entity, _, _, _ = await _create_entries(hass, client)
|
||||
mock_twinkly_client.is_on.return_value = False
|
||||
|
||||
assert hass.states.get(entity.entity_id).state == "off"
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
service_data={"entity_id": entity.entity_id, "brightness": 255},
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
service_data={ATTR_ENTITY_ID: "light.tree_1", ATTR_BRIGHTNESS: 255},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
mock_twinkly_client.set_brightness.assert_called_once_with(100)
|
||||
mock_twinkly_client.turn_on.assert_called_once_with()
|
||||
|
||||
assert state.state == "on"
|
||||
assert state.attributes[ATTR_BRIGHTNESS] == 255
|
||||
|
||||
async def test_brightness_to_zero(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_twinkly_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test support of the light.turn_on service with a brightness parameter."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
service_data={"entity_id": entity.entity_id, "brightness": 1},
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
service_data={ATTR_ENTITY_ID: "light.tree_1", ATTR_BRIGHTNESS: 1},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
|
||||
assert state.state == "off"
|
||||
mock_twinkly_client.set_brightness.assert_not_called()
|
||||
mock_twinkly_client.turn_off.assert_called_once_with()
|
||||
|
||||
|
||||
async def test_turn_on_with_color_rgbw(hass: HomeAssistant) -> None:
|
||||
async def test_turn_on_with_color_rgbw(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_twinkly_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test support of the light.turn_on service with a rgbw parameter."""
|
||||
client = ClientMock()
|
||||
client.state = False
|
||||
client.device_info["led_profile"] = "RGBW"
|
||||
client.brightness = {"mode": "enabled", "value": 255}
|
||||
entity, _, _, _ = await _create_entries(hass, client)
|
||||
mock_twinkly_client.is_on.return_value = False
|
||||
mock_twinkly_client.get_details.return_value["led_profile"] = "RGBW"
|
||||
|
||||
assert hass.states.get(entity.entity_id).state == "off"
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
assert (
|
||||
LightEntityFeature.EFFECT
|
||||
& hass.states.get(entity.entity_id).attributes["supported_features"]
|
||||
& hass.states.get("light.tree_1").attributes[ATTR_SUPPORTED_FEATURES]
|
||||
)
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
service_data={"entity_id": entity.entity_id, "rgbw_color": (128, 64, 32, 0)},
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
service_data={
|
||||
ATTR_ENTITY_ID: "light.tree_1",
|
||||
ATTR_RGBW_COLOR: (128, 64, 32, 0),
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
|
||||
assert state.state == "on"
|
||||
assert client.color == (128, 64, 32)
|
||||
assert client.default_mode == "color"
|
||||
assert client.mode == "color"
|
||||
mock_twinkly_client.interview.assert_called_once_with()
|
||||
mock_twinkly_client.set_static_colour.assert_called_once_with((128, 64, 32))
|
||||
mock_twinkly_client.set_mode.assert_called_once_with("color")
|
||||
assert mock_twinkly_client.default_mode == "color"
|
||||
|
||||
|
||||
async def test_turn_on_with_color_rgb(hass: HomeAssistant) -> None:
|
||||
async def test_turn_on_with_color_rgb(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_twinkly_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test support of the light.turn_on service with a rgb parameter."""
|
||||
client = ClientMock()
|
||||
client.state = False
|
||||
client.device_info["led_profile"] = "RGB"
|
||||
client.brightness = {"mode": "enabled", "value": 255}
|
||||
entity, _, _, _ = await _create_entries(hass, client)
|
||||
mock_twinkly_client.is_on.return_value = False
|
||||
mock_twinkly_client.get_details.return_value["led_profile"] = "RGB"
|
||||
|
||||
assert hass.states.get(entity.entity_id).state == "off"
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
assert (
|
||||
LightEntityFeature.EFFECT
|
||||
& hass.states.get(entity.entity_id).attributes["supported_features"]
|
||||
& hass.states.get("light.tree_1").attributes[ATTR_SUPPORTED_FEATURES]
|
||||
)
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
service_data={"entity_id": entity.entity_id, "rgb_color": (128, 64, 32)},
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
service_data={ATTR_ENTITY_ID: "light.tree_1", ATTR_RGB_COLOR: (128, 64, 32)},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
|
||||
assert state.state == "on"
|
||||
assert client.color == (128, 64, 32)
|
||||
assert client.default_mode == "color"
|
||||
assert client.mode == "color"
|
||||
mock_twinkly_client.interview.assert_called_once_with()
|
||||
mock_twinkly_client.set_static_colour.assert_called_once_with((128, 64, 32))
|
||||
mock_twinkly_client.set_mode.assert_called_once_with("color")
|
||||
assert mock_twinkly_client.default_mode == "color"
|
||||
|
||||
|
||||
async def test_turn_on_with_effect(hass: HomeAssistant) -> None:
|
||||
async def test_turn_on_with_effect(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_twinkly_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test support of the light.turn_on service with effects."""
|
||||
client = ClientMock()
|
||||
client.state = False
|
||||
client.device_info["led_profile"] = "RGB"
|
||||
client.brightness = {"mode": "enabled", "value": 255}
|
||||
entity, _, _, _ = await _create_entries(hass, client)
|
||||
mock_twinkly_client.is_on.return_value = False
|
||||
mock_twinkly_client.get_details.return_value["led_profile"] = "RGB"
|
||||
|
||||
assert hass.states.get(entity.entity_id).state == "off"
|
||||
assert not client.current_movie
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
assert (
|
||||
LightEntityFeature.EFFECT
|
||||
& hass.states.get(entity.entity_id).attributes["supported_features"]
|
||||
& hass.states.get("light.tree_1").attributes[ATTR_SUPPORTED_FEATURES]
|
||||
)
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
service_data={"entity_id": entity.entity_id, "effect": "1 Rainbow"},
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
service_data={ATTR_ENTITY_ID: "light.tree_1", ATTR_EFFECT: "2 Rainbow"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
|
||||
assert state.state == "on"
|
||||
assert client.current_movie["id"] == 1
|
||||
assert client.default_mode == "movie"
|
||||
assert client.mode == "movie"
|
||||
mock_twinkly_client.interview.assert_called_once_with()
|
||||
mock_twinkly_client.set_current_movie.assert_called_once_with(2)
|
||||
mock_twinkly_client.set_mode.assert_called_once_with("movie")
|
||||
assert mock_twinkly_client.default_mode == "movie"
|
||||
|
||||
|
||||
async def test_turn_on_with_color_rgbw_and_missing_effect(hass: HomeAssistant) -> None:
|
||||
@pytest.mark.parametrize(
|
||||
("data"),
|
||||
[
|
||||
{ATTR_RGBW_COLOR: (128, 64, 32, 0)},
|
||||
{ATTR_RGB_COLOR: (128, 64, 32)},
|
||||
],
|
||||
)
|
||||
async def test_turn_on_with_missing_effect(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_twinkly_client: AsyncMock,
|
||||
data: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test support of the light.turn_on service with rgbw color and missing effect support."""
|
||||
client = ClientMock()
|
||||
client.state = False
|
||||
client.device_info["led_profile"] = "RGBW"
|
||||
client.brightness = {"mode": "enabled", "value": 255}
|
||||
client.version = "2.7.0"
|
||||
entity, _, _, _ = await _create_entries(hass, client)
|
||||
mock_twinkly_client.is_on.return_value = False
|
||||
mock_twinkly_client.get_firmware_version.return_value["version"] = "2.7.0"
|
||||
|
||||
assert hass.states.get(entity.entity_id).state == "off"
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
assert (
|
||||
not LightEntityFeature.EFFECT
|
||||
& hass.states.get(entity.entity_id).attributes["supported_features"]
|
||||
LightEntityFeature.EFFECT
|
||||
^ hass.states.get("light.tree_1").attributes[ATTR_SUPPORTED_FEATURES]
|
||||
)
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
service_data={"entity_id": entity.entity_id, "rgbw_color": (128, 64, 32, 0)},
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
service_data={ATTR_ENTITY_ID: "light.tree_1"} | data,
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
|
||||
assert state.state == "on"
|
||||
assert client.color == (0, 128, 64, 32)
|
||||
assert client.mode == "movie"
|
||||
assert client.default_mode == "movie"
|
||||
mock_twinkly_client.interview.assert_called_once_with()
|
||||
mock_twinkly_client.set_cycle_colours.assert_called_once_with((128, 64, 32))
|
||||
mock_twinkly_client.set_mode.assert_called_once_with("movie")
|
||||
assert mock_twinkly_client.default_mode == "movie"
|
||||
mock_twinkly_client.set_current_movie.assert_not_called()
|
||||
|
||||
|
||||
async def test_turn_on_with_color_rgb_and_missing_effect(hass: HomeAssistant) -> None:
|
||||
"""Test support of the light.turn_on service with rgb color and missing effect support."""
|
||||
client = ClientMock()
|
||||
client.state = False
|
||||
client.device_info["led_profile"] = "RGB"
|
||||
client.brightness = {"mode": "enabled", "value": 255}
|
||||
client.version = "2.7.0"
|
||||
entity, _, _, _ = await _create_entries(hass, client)
|
||||
async def test_turn_on_with_color_rgbw_and_missing_effect(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_twinkly_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test support of the light.turn_on service with missing effect support."""
|
||||
mock_twinkly_client.is_on.return_value = False
|
||||
mock_twinkly_client.get_firmware_version.return_value["version"] = "2.7.0"
|
||||
|
||||
assert hass.states.get(entity.entity_id).state == "off"
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
assert (
|
||||
not LightEntityFeature.EFFECT
|
||||
& hass.states.get(entity.entity_id).attributes["supported_features"]
|
||||
LightEntityFeature.EFFECT
|
||||
^ hass.states.get("light.tree_1").attributes[ATTR_SUPPORTED_FEATURES]
|
||||
)
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
service_data={"entity_id": entity.entity_id, "rgb_color": (128, 64, 32)},
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
service_data={ATTR_ENTITY_ID: "light.tree_1", ATTR_EFFECT: "2 Rainbow"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
|
||||
assert state.state == "on"
|
||||
assert client.color == (128, 64, 32)
|
||||
assert client.mode == "movie"
|
||||
assert client.default_mode == "movie"
|
||||
mock_twinkly_client.set_current_movie.assert_not_called()
|
||||
|
||||
|
||||
async def test_turn_on_with_effect_missing_effects(hass: HomeAssistant) -> None:
|
||||
"""Test support of the light.turn_on service with effect set even if effects are not supported."""
|
||||
client = ClientMock()
|
||||
client.state = False
|
||||
client.device_info["led_profile"] = "RGB"
|
||||
client.brightness = {"mode": "enabled", "value": 255}
|
||||
client.version = "2.7.0"
|
||||
entity, _, _, _ = await _create_entries(hass, client)
|
||||
|
||||
assert hass.states.get(entity.entity_id).state == "off"
|
||||
assert not client.current_movie
|
||||
assert (
|
||||
not LightEntityFeature.EFFECT
|
||||
& hass.states.get(entity.entity_id).attributes["supported_features"]
|
||||
)
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
service_data={"entity_id": entity.entity_id, "effect": "1 Rainbow"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
|
||||
assert state.state == "on"
|
||||
assert not client.current_movie
|
||||
assert client.default_mode == "movie"
|
||||
assert client.mode == "movie"
|
||||
|
||||
|
||||
async def test_turn_off(hass: HomeAssistant) -> None:
|
||||
async def test_turn_off(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_twinkly_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test support of the light.turn_off service."""
|
||||
entity, _, _, _ = await _create_entries(hass)
|
||||
|
||||
assert hass.states.get(entity.entity_id).state == "on"
|
||||
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
await hass.services.async_call(
|
||||
"light", "turn_off", service_data={"entity_id": entity.entity_id}, blocking=True
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
service_data={ATTR_ENTITY_ID: "light.tree_1"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
|
||||
assert state.state == "off"
|
||||
mock_twinkly_client.turn_off.assert_called_once_with()
|
||||
|
||||
|
||||
async def test_update_name(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_twinkly_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Validate device's name update behavior.
|
||||
|
||||
@ -294,56 +291,15 @@ async def test_update_name(
|
||||
then the name of the entity is updated and it's also persisted,
|
||||
so it can be restored when starting HA while Twinkly is offline.
|
||||
"""
|
||||
entity, _, client, config_entry = await _create_entries(hass)
|
||||
|
||||
client.change_name("new_device_name")
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
mock_twinkly_client.get_details.return_value["device_name"] = "new_device_name"
|
||||
|
||||
freezer.tick(timedelta(seconds=30))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
dev_entry = device_registry.async_get_device({(TWINKLY_DOMAIN, TEST_MAC)})
|
||||
dev_entry = device_registry.async_get_device({(DOMAIN, TEST_MAC)})
|
||||
|
||||
assert dev_entry.name == "new_device_name"
|
||||
assert config_entry.data[CONF_NAME] == "new_device_name"
|
||||
|
||||
|
||||
async def test_unload(hass: HomeAssistant) -> None:
|
||||
"""Validate that entities can be unloaded from the UI."""
|
||||
|
||||
_, _, _, entry = await _create_entries(hass)
|
||||
|
||||
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||
|
||||
|
||||
async def _create_entries(
|
||||
hass: HomeAssistant, client=None
|
||||
) -> tuple[RegistryEntry, DeviceEntry, ClientMock]:
|
||||
client = ClientMock() if client is None else client
|
||||
|
||||
with patch("homeassistant.components.twinkly.Twinkly", return_value=client):
|
||||
config_entry = MockConfigEntry(
|
||||
domain=TWINKLY_DOMAIN,
|
||||
data={
|
||||
CONF_HOST: client,
|
||||
CONF_ID: client.id,
|
||||
CONF_NAME: TEST_NAME_ORIGINAL,
|
||||
CONF_MODEL: TEST_MODEL,
|
||||
},
|
||||
unique_id=TEST_MAC,
|
||||
minor_version=2,
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device_registry = dr.async_get(hass)
|
||||
entity_registry = er.async_get(hass)
|
||||
|
||||
entity_id = entity_registry.async_get_entity_id("light", TWINKLY_DOMAIN, TEST_MAC)
|
||||
entity_entry = entity_registry.async_get(entity_id)
|
||||
device = device_registry.async_get_device(identifiers={(TWINKLY_DOMAIN, TEST_MAC)})
|
||||
|
||||
assert entity_entry is not None
|
||||
assert device is not None
|
||||
|
||||
return entity_entry, device, client, config_entry
|
||||
|
Loading…
x
Reference in New Issue
Block a user