Refactor Twinkly tests (#133725)

This commit is contained in:
Joost Lekkerkerker 2024-12-22 12:00:24 +01:00 committed by GitHub
parent 31c6443a9b
commit 7be3cad1db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 575 additions and 623 deletions

View File

@ -17,7 +17,7 @@
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
}, },
"abort": { "abort": {
"device_exists": "[%key:common::config_flow::abort::already_configured_device%]" "already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
} }
} }
} }

View File

@ -1,120 +1,13 @@
"""Constants and mock for the twinkly component tests.""" """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 from tests.common import MockConfigEntry
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"
class ClientMock: async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
"""A mock of the ttls.client.Twinkly.""" """Fixture for setting up the component."""
config_entry.add_to_hass(hass)
def __init__(self) -> None: await hass.config_entries.async_setup(config_entry.entry_id)
"""Create a mocked client.""" await hass.async_block_till_done()
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}

View File

@ -1,55 +1,74 @@
"""Configure tests for the Twinkly integration.""" """Configure tests for the Twinkly integration."""
from collections.abc import Awaitable, Callable, Coroutine from collections.abc import Generator
from typing import Any from unittest.mock import AsyncMock, patch
from unittest.mock import patch
import pytest import pytest
from homeassistant.core import HomeAssistant from homeassistant.components.twinkly import DOMAIN
from homeassistant.setup import async_setup_component 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 from tests.common import (
MockConfigEntry,
type ComponentSetup = Callable[[], Awaitable[ClientMock]] load_json_array_fixture,
load_json_object_fixture,
DOMAIN = "twinkly" )
TITLE = "Twinkly"
@pytest.fixture(name="config_entry") @pytest.fixture
def mock_config_entry() -> MockConfigEntry: def mock_config_entry() -> MockConfigEntry:
"""Create Twinkly entry in Home Assistant.""" """Create Twinkly entry in Home Assistant."""
client = ClientMock()
return MockConfigEntry( return MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
title=TITLE, title="Twinkly",
unique_id=TEST_UID, unique_id=TEST_MAC,
entry_id=TEST_UID,
data={ data={
"host": client.host, CONF_HOST: "192.168.0.123",
"id": client.id, CONF_ID: "497dcba3-ecbf-4587-a2dd-5eb0665e6880",
"name": TEST_NAME, CONF_NAME: TEST_NAME,
"model": TEST_MODEL, CONF_MODEL: TEST_MODEL,
"device_name": TEST_NAME,
}, },
entry_id="01JFMME2P6RA38V5AMPCJ2JYYV",
minor_version=2,
) )
@pytest.fixture(name="setup_integration") @pytest.fixture
async def mock_setup_integration( def mock_twinkly_client() -> Generator[AsyncMock]:
hass: HomeAssistant, config_entry: MockConfigEntry """Mock the Twinkly client."""
) -> Callable[[], Coroutine[Any, Any, ClientMock]]: with (
"""Fixture for setting up the component.""" patch(
config_entry.add_to_hass(hass) "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

View 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"

View File

@ -0,0 +1,3 @@
{
"id": 1
}

View 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
}

View File

@ -0,0 +1 @@
{ "version": "2.7.2" }

View File

@ -0,0 +1,4 @@
[
{ "id": 1, "name": "Rainbow" },
{ "id": 2, "name": "Flare" }
]

View File

@ -3,35 +3,64 @@
dict({ dict({
'attributes': dict({ 'attributes': dict({
'brightness': 26, 'brightness': 26,
'color_mode': 'brightness', 'color_mode': 'rgb',
'effect': None, 'effect': None,
'effect_list': list([ '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([ 'supported_color_modes': list([
'brightness', 'rgb',
]), ]),
'supported_features': 4, 'supported_features': 4,
'xy_color': list([
0.323,
0.329,
]),
}), }),
'device_info': dict({ '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**', 'mac': '**REDACTED**',
'product_code': 'twinkly_test_device_model', 'max_supported_led': 100,
'uuid': '4c8fccf5-e08a-4173-92d5-49bf479252a2', '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({ 'entry': dict({
'data': dict({ 'data': dict({
'device_name': 'twinkly_test_device_name',
'host': '**REDACTED**', 'host': '**REDACTED**',
'id': '4c8fccf5-e08a-4173-92d5-49bf479252a2', 'id': '497dcba3-ecbf-4587-a2dd-5eb0665e6880',
'model': 'twinkly_test_device_model', 'model': 'TW2016',
'name': 'twinkly_test_device_name', 'name': 'Tree 1',
}), }),
'disabled_by': None, 'disabled_by': None,
'discovery_keys': dict({ 'discovery_keys': dict({
}), }),
'domain': 'twinkly', 'domain': 'twinkly',
'entry_id': '4c8fccf5-e08a-4173-92d5-49bf479252a2', 'entry_id': '01JFMME2P6RA38V5AMPCJ2JYYV',
'minor_version': 2, 'minor_version': 2,
'options': dict({ 'options': dict({
}), }),
@ -39,9 +68,9 @@
'pref_disable_polling': False, 'pref_disable_polling': False,
'source': 'user', 'source': 'user',
'title': 'Twinkly', 'title': 'Twinkly',
'unique_id': 'aa:bb:cc:dd:ee:ff', 'unique_id': '00:2d:13:3b:aa:bb',
'version': 1, 'version': 1,
}), }),
'sw_version': '2.8.10', 'sw_version': '2.7.2',
}) })
# --- # ---

View 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',
})
# ---

View File

@ -1,196 +1,170 @@
"""Tests for the config_flow of the twinly component.""" """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 import dhcp
from homeassistant.components.twinkly.const import DOMAIN as TWINKLY_DOMAIN from homeassistant.components.twinkly.const import DOMAIN
from homeassistant.config_entries import SOURCE_USER from homeassistant.config_entries import SOURCE_DHCP, SOURCE_USER
from homeassistant.const import CONF_HOST, CONF_ID, CONF_MODEL, CONF_NAME from homeassistant.const import CONF_HOST, CONF_ID, CONF_MODEL, CONF_NAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType 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 from tests.common import MockConfigEntry
async def test_invalid_host(hass: HomeAssistant) -> None: @pytest.mark.usefixtures("mock_twinkly_client", "mock_setup_entry")
"""Test the failure when invalid host provided.""" async def test_full_flow(hass: HomeAssistant) -> None:
client = ClientMock() """Test the full flow."""
client.is_offline = True result = await hass.config_entries.flow.async_init(
with patch( DOMAIN,
"homeassistant.components.twinkly.config_flow.Twinkly", return_value=client context={"source": SOURCE_USER},
): )
result = await hass.config_entries.flow.async_init( assert result["type"] is FlowResultType.FORM
TWINKLY_DOMAIN, context={"source": config_entries.SOURCE_USER} assert result["step_id"] == "user"
) assert result["errors"] == {}
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user" result = await hass.config_entries.flow.async_configure(
assert result["errors"] == {} result["flow_id"],
result = await hass.config_entries.flow.async_configure( {CONF_HOST: "192.168.0.123"},
result["flow_id"], )
{CONF_HOST: "dummy"}, 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["type"] is FlowResultType.FORM
assert result["step_id"] == "user" assert result["step_id"] == "user"
assert result["errors"] == {CONF_HOST: "cannot_connect"} assert result["errors"] == {CONF_HOST: "cannot_connect"}
mock_twinkly_client.get_details.side_effect = None
async def test_success_flow(hass: HomeAssistant) -> None: result = await hass.config_entries.flow.async_configure(
"""Test that an entity is created when the flow completes.""" result["flow_id"],
client = ClientMock() {CONF_HOST: "192.168.0.123"},
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,
) )
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["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured" 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: async def test_user_flow_works_discovery(hass: HomeAssistant) -> None:
"""Test user flow can continue after discovery happened.""" """Test user flow can continue after discovery happened."""
client = ClientMock() await hass.config_entries.flow.async_init(
with ( DOMAIN,
patch( context={"source": SOURCE_DHCP},
"homeassistant.components.twinkly.config_flow.Twinkly", return_value=client data=dhcp.DhcpServiceInfo(
hostname="Twinkly_XYZ",
ip="1.2.3.4",
macaddress="002d133baabb",
), ),
patch("homeassistant.components.twinkly.async_setup_entry", return_value=True), )
): result = await hass.config_entries.flow.async_init(
await hass.config_entries.flow.async_init( DOMAIN,
TWINKLY_DOMAIN, context={"source": SOURCE_USER},
context={"source": config_entries.SOURCE_DHCP}, )
data=dhcp.DhcpServiceInfo( assert len(hass.config_entries.flow.async_progress(DOMAIN)) == 2
hostname="Twinkly_XYZ", assert result["type"] is FlowResultType.FORM
ip="1.2.3.4", assert result["step_id"] == "user"
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_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{CONF_HOST: "10.0.0.131"}, {CONF_HOST: "10.0.0.131"},
) )
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
# Verify the discovery flow was aborted # Verify the discovery flow was aborted
assert not hass.config_entries.flow.async_progress(TWINKLY_DOMAIN) assert not hass.config_entries.flow.async_progress(DOMAIN)

View File

@ -1,32 +1,28 @@
"""Tests for the diagnostics of the twinkly component.""" """Tests for the diagnostics of the twinkly component."""
from collections.abc import Awaitable, Callable import pytest
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
from syrupy.filters import props from syrupy.filters import props
from homeassistant.core import HomeAssistant 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.components.diagnostics import get_diagnostics_for_config_entry
from tests.typing import ClientSessionGenerator from tests.typing import ClientSessionGenerator
type ComponentSetup = Callable[[], Awaitable[ClientMock]]
DOMAIN = "twinkly"
@pytest.mark.usefixtures("mock_twinkly_client")
async def test_diagnostics( async def test_diagnostics(
hass: HomeAssistant, hass: HomeAssistant,
hass_client: ClientSessionGenerator, hass_client: ClientSessionGenerator,
setup_integration: ComponentSetup, mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
) -> None: ) -> None:
"""Test diagnostics.""" """Test diagnostics."""
await setup_integration() await setup_integration(hass, mock_config_entry)
entry = hass.config_entries.async_entries(DOMAIN)[0]
assert await get_diagnostics_for_config_entry(hass, hass_client, entry) == snapshot( assert await get_diagnostics_for_config_entry(
exclude=props("created_at", "modified_at") hass, hass_client, mock_config_entry
) ) == snapshot(exclude=props("created_at", "modified_at"))

View File

@ -1,7 +1,9 @@
"""Tests of the initialization of the twinkly integration.""" """Tests of the initialization of the twinkly integration."""
from unittest.mock import patch from unittest.mock import AsyncMock
from uuid import uuid4
from aiohttp import ClientConnectionError
import pytest
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from homeassistant.components.twinkly.const import 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.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er 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 from tests.common import MockConfigEntry
async def test_load_unload_entry(hass: HomeAssistant) -> None: @pytest.mark.usefixtures("mock_twinkly_client")
"""Validate that setup entry also configure the client.""" async def test_load_unload_entry(
client = ClientMock() hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Test the load/unload of the config entry."""
device_id = str(uuid4()) await setup_integration(hass, mock_config_entry)
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,
)
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_unload(mock_config_entry.entry_id)
await hass.config_entries.async_setup(config_entry.entry_id)
assert config_entry.state is ConfigEntryState.LOADED assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
await hass.config_entries.async_unload(config_entry.entry_id)
assert 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.""" """Validate that config entry is retried."""
client = ClientMock() mock_twinkly_client.get_details.side_effect = ClientConnectionError
client.is_offline = True
config_entry = MockConfigEntry( await setup_integration(hass, mock_config_entry)
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,
)
config_entry.add_to_hass(hass) assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
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
@pytest.mark.usefixtures("mock_twinkly_client")
async def test_mac_migration( async def test_mac_migration(
hass: HomeAssistant, hass: HomeAssistant,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
device_registry: dr.DeviceRegistry, device_registry: dr.DeviceRegistry,
) -> None: ) -> None:
"""Validate that the unique_id is migrated to the MAC address.""" """Validate that the unique_id is migrated to the MAC address."""
client = ClientMock()
config_entry = MockConfigEntry( config_entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
minor_version=1, minor_version=1,
unique_id="unique_id", unique_id="unique_id",
data={ data={
CONF_HOST: TEST_HOST, CONF_HOST: "192.168.0.123",
CONF_ID: id, CONF_ID: id,
CONF_NAME: TEST_NAME_ORIGINAL, CONF_NAME: "Tree 1",
CONF_MODEL: TEST_MODEL, CONF_MODEL: TEST_MODEL,
}, },
) )
@ -100,8 +75,7 @@ async def test_mac_migration(
identifiers={(DOMAIN, config_entry.unique_id)}, 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 assert config_entry.state is ConfigEntryState.LOADED

View File

@ -3,290 +3,287 @@
from __future__ import annotations from __future__ import annotations
from datetime import timedelta from datetime import timedelta
from unittest.mock import patch from typing import Any
from unittest.mock import AsyncMock, patch
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy import SnapshotAssertion
from homeassistant.components.light import ATTR_BRIGHTNESS, LightEntityFeature from homeassistant.components.light import (
from homeassistant.components.twinkly.const import DOMAIN as TWINKLY_DOMAIN ATTR_BRIGHTNESS,
from homeassistant.const import CONF_HOST, CONF_ID, CONF_MODEL, CONF_NAME 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.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er 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: @pytest.mark.usefixtures("mock_twinkly_client")
"""Validate that entity and device states are updated on startup.""" async def test_entities(
entity, device, _, _ = await _create_entries(hass) 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) await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_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"
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.""" """Test support of the light.turn_on service."""
client = ClientMock() mock_twinkly_client.is_on.return_value = False
client.state = False
client.brightness = {"mode": "enabled", "value": 20}
entity, _, _, _ = await _create_entries(hass, client)
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( 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) mock_twinkly_client.turn_on.assert_called_once_with()
assert state.state == "on"
assert state.attributes[ATTR_BRIGHTNESS] == 51
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.""" """Test support of the light.turn_on service with a brightness parameter."""
client = ClientMock() mock_twinkly_client.is_on.return_value = False
client.state = False
client.brightness = {"mode": "enabled", "value": 20}
entity, _, _, _ = await _create_entries(hass, client)
assert hass.states.get(entity.entity_id).state == "off" await setup_integration(hass, mock_config_entry)
await hass.services.async_call( await hass.services.async_call(
"light", LIGHT_DOMAIN,
"turn_on", SERVICE_TURN_ON,
service_data={"entity_id": entity.entity_id, "brightness": 255}, service_data={ATTR_ENTITY_ID: "light.tree_1", ATTR_BRIGHTNESS: 255},
blocking=True, 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( await hass.services.async_call(
"light", LIGHT_DOMAIN,
"turn_on", SERVICE_TURN_ON,
service_data={"entity_id": entity.entity_id, "brightness": 1}, service_data={ATTR_ENTITY_ID: "light.tree_1", ATTR_BRIGHTNESS: 1},
blocking=True, blocking=True,
) )
state = hass.states.get(entity.entity_id) mock_twinkly_client.set_brightness.assert_not_called()
mock_twinkly_client.turn_off.assert_called_once_with()
assert state.state == "off"
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.""" """Test support of the light.turn_on service with a rgbw parameter."""
client = ClientMock() mock_twinkly_client.is_on.return_value = False
client.state = False mock_twinkly_client.get_details.return_value["led_profile"] = "RGBW"
client.device_info["led_profile"] = "RGBW"
client.brightness = {"mode": "enabled", "value": 255}
entity, _, _, _ = await _create_entries(hass, client)
assert hass.states.get(entity.entity_id).state == "off" await setup_integration(hass, mock_config_entry)
assert ( assert (
LightEntityFeature.EFFECT 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( await hass.services.async_call(
"light", LIGHT_DOMAIN,
"turn_on", SERVICE_TURN_ON,
service_data={"entity_id": entity.entity_id, "rgbw_color": (128, 64, 32, 0)}, service_data={
ATTR_ENTITY_ID: "light.tree_1",
ATTR_RGBW_COLOR: (128, 64, 32, 0),
},
blocking=True, blocking=True,
) )
state = hass.states.get(entity.entity_id) mock_twinkly_client.interview.assert_called_once_with()
mock_twinkly_client.set_static_colour.assert_called_once_with((128, 64, 32))
assert state.state == "on" mock_twinkly_client.set_mode.assert_called_once_with("color")
assert client.color == (128, 64, 32) assert mock_twinkly_client.default_mode == "color"
assert client.default_mode == "color"
assert client.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.""" """Test support of the light.turn_on service with a rgb parameter."""
client = ClientMock() mock_twinkly_client.is_on.return_value = False
client.state = False mock_twinkly_client.get_details.return_value["led_profile"] = "RGB"
client.device_info["led_profile"] = "RGB"
client.brightness = {"mode": "enabled", "value": 255}
entity, _, _, _ = await _create_entries(hass, client)
assert hass.states.get(entity.entity_id).state == "off" await setup_integration(hass, mock_config_entry)
assert ( assert (
LightEntityFeature.EFFECT 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( await hass.services.async_call(
"light", LIGHT_DOMAIN,
"turn_on", SERVICE_TURN_ON,
service_data={"entity_id": entity.entity_id, "rgb_color": (128, 64, 32)}, service_data={ATTR_ENTITY_ID: "light.tree_1", ATTR_RGB_COLOR: (128, 64, 32)},
blocking=True, blocking=True,
) )
state = hass.states.get(entity.entity_id) mock_twinkly_client.interview.assert_called_once_with()
mock_twinkly_client.set_static_colour.assert_called_once_with((128, 64, 32))
assert state.state == "on" mock_twinkly_client.set_mode.assert_called_once_with("color")
assert client.color == (128, 64, 32) assert mock_twinkly_client.default_mode == "color"
assert client.default_mode == "color"
assert client.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.""" """Test support of the light.turn_on service with effects."""
client = ClientMock() mock_twinkly_client.is_on.return_value = False
client.state = False mock_twinkly_client.get_details.return_value["led_profile"] = "RGB"
client.device_info["led_profile"] = "RGB"
client.brightness = {"mode": "enabled", "value": 255}
entity, _, _, _ = await _create_entries(hass, client)
assert hass.states.get(entity.entity_id).state == "off" await setup_integration(hass, mock_config_entry)
assert not client.current_movie
assert ( assert (
LightEntityFeature.EFFECT 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( await hass.services.async_call(
"light", LIGHT_DOMAIN,
"turn_on", SERVICE_TURN_ON,
service_data={"entity_id": entity.entity_id, "effect": "1 Rainbow"}, service_data={ATTR_ENTITY_ID: "light.tree_1", ATTR_EFFECT: "2 Rainbow"},
blocking=True, blocking=True,
) )
state = hass.states.get(entity.entity_id) mock_twinkly_client.interview.assert_called_once_with()
mock_twinkly_client.set_current_movie.assert_called_once_with(2)
assert state.state == "on" mock_twinkly_client.set_mode.assert_called_once_with("movie")
assert client.current_movie["id"] == 1 assert mock_twinkly_client.default_mode == "movie"
assert client.default_mode == "movie"
assert client.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.""" """Test support of the light.turn_on service with rgbw color and missing effect support."""
client = ClientMock() mock_twinkly_client.is_on.return_value = False
client.state = False mock_twinkly_client.get_firmware_version.return_value["version"] = "2.7.0"
client.device_info["led_profile"] = "RGBW"
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" await setup_integration(hass, mock_config_entry)
assert ( assert (
not LightEntityFeature.EFFECT 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( await hass.services.async_call(
"light", LIGHT_DOMAIN,
"turn_on", SERVICE_TURN_ON,
service_data={"entity_id": entity.entity_id, "rgbw_color": (128, 64, 32, 0)}, service_data={ATTR_ENTITY_ID: "light.tree_1"} | data,
blocking=True, blocking=True,
) )
state = hass.states.get(entity.entity_id) mock_twinkly_client.interview.assert_called_once_with()
mock_twinkly_client.set_cycle_colours.assert_called_once_with((128, 64, 32))
assert state.state == "on" mock_twinkly_client.set_mode.assert_called_once_with("movie")
assert client.color == (0, 128, 64, 32) assert mock_twinkly_client.default_mode == "movie"
assert client.mode == "movie" mock_twinkly_client.set_current_movie.assert_not_called()
assert client.default_mode == "movie"
async def test_turn_on_with_color_rgb_and_missing_effect(hass: HomeAssistant) -> None: async def test_turn_on_with_color_rgbw_and_missing_effect(
"""Test support of the light.turn_on service with rgb color and missing effect support.""" hass: HomeAssistant,
client = ClientMock() mock_config_entry: MockConfigEntry,
client.state = False mock_twinkly_client: AsyncMock,
client.device_info["led_profile"] = "RGB" ) -> None:
client.brightness = {"mode": "enabled", "value": 255} """Test support of the light.turn_on service with missing effect support."""
client.version = "2.7.0" mock_twinkly_client.is_on.return_value = False
entity, _, _, _ = await _create_entries(hass, client) 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 ( assert (
not LightEntityFeature.EFFECT 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( await hass.services.async_call(
"light", LIGHT_DOMAIN,
"turn_on", SERVICE_TURN_ON,
service_data={"entity_id": entity.entity_id, "rgb_color": (128, 64, 32)}, service_data={ATTR_ENTITY_ID: "light.tree_1", ATTR_EFFECT: "2 Rainbow"},
blocking=True, blocking=True,
) )
state = hass.states.get(entity.entity_id) mock_twinkly_client.set_current_movie.assert_not_called()
assert state.state == "on"
assert client.color == (128, 64, 32)
assert client.mode == "movie"
assert client.default_mode == "movie"
async def test_turn_on_with_effect_missing_effects(hass: HomeAssistant) -> None: async def test_turn_off(
"""Test support of the light.turn_on service with effect set even if effects are not supported.""" hass: HomeAssistant,
client = ClientMock() mock_config_entry: MockConfigEntry,
client.state = False mock_twinkly_client: AsyncMock,
client.device_info["led_profile"] = "RGB" ) -> None:
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:
"""Test support of the light.turn_off service.""" """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( 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,
) )
mock_twinkly_client.turn_off.assert_called_once_with()
state = hass.states.get(entity.entity_id)
assert state.state == "off"
async def test_update_name( async def test_update_name(
hass: HomeAssistant, hass: HomeAssistant,
device_registry: dr.DeviceRegistry, device_registry: dr.DeviceRegistry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
mock_config_entry: MockConfigEntry,
mock_twinkly_client: AsyncMock,
) -> None: ) -> None:
"""Validate device's name update behavior. """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, 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. 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)) freezer.tick(timedelta(seconds=30))
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done() 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 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