Cleanups in Twinkly code (#64139)

* Cleanup Twinkly code

* Add codeowner'

* Change const names
pull/64159/head
Rob Bierbooms 2022-01-14 22:07:15 +01:00 committed by GitHub
parent e609f196bc
commit efe34c8d13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 124 additions and 194 deletions

View File

@ -973,8 +973,8 @@ homeassistant/components/tuya/* @Tuya @zlinoliver @METISU @frenck
tests/components/tuya/* @Tuya @zlinoliver @METISU @frenck tests/components/tuya/* @Tuya @zlinoliver @METISU @frenck
homeassistant/components/twentemilieu/* @frenck homeassistant/components/twentemilieu/* @frenck
tests/components/twentemilieu/* @frenck tests/components/twentemilieu/* @frenck
homeassistant/components/twinkly/* @dr1rrb homeassistant/components/twinkly/* @dr1rrb @Robbie1221
tests/components/twinkly/* @dr1rrb tests/components/twinkly/* @dr1rrb @Robbie1221
homeassistant/components/ubus/* @noltari homeassistant/components/ubus/* @noltari
homeassistant/components/unifi/* @Kane610 homeassistant/components/unifi/* @Kane610
tests/components/unifi/* @Kane610 tests/components/unifi/* @Kane610

View File

@ -11,7 +11,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import CONF_ENTRY_HOST, CONF_ENTRY_ID, DATA_CLIENT, DATA_DEVICE_INFO, DOMAIN from .const import CONF_HOST, DATA_CLIENT, DATA_DEVICE_INFO, DOMAIN
PLATFORMS = [Platform.LIGHT] PLATFORMS = [Platform.LIGHT]
@ -22,10 +22,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# We setup the client here so if at some point we add any other entity for this device, # We setup the client here so if at some point we add any other entity for this device,
# we will be able to properly share the connection. # we will be able to properly share the connection.
uuid = entry.data[CONF_ENTRY_ID] host = entry.data[CONF_HOST]
host = entry.data[CONF_ENTRY_HOST]
hass.data[DOMAIN].setdefault(uuid, {}) hass.data[DOMAIN].setdefault(entry.entry_id, {})
client = Twinkly(host, async_get_clientsession(hass)) client = Twinkly(host, async_get_clientsession(hass))
@ -34,8 +33,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
except (asyncio.TimeoutError, ClientError) as exception: except (asyncio.TimeoutError, ClientError) as exception:
raise ConfigEntryNotReady from exception raise ConfigEntryNotReady from exception
hass.data[DOMAIN][uuid][DATA_CLIENT] = client hass.data[DOMAIN][entry.entry_id][DATA_CLIENT] = client
hass.data[DOMAIN][uuid][DATA_DEVICE_INFO] = device_info hass.data[DOMAIN][entry.entry_id][DATA_DEVICE_INFO] = device_info
hass.config_entries.async_setup_platforms(entry, PLATFORMS) hass.config_entries.async_setup_platforms(entry, PLATFORMS)
@ -45,9 +44,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Remove a twinkly entry.""" """Remove a twinkly entry."""
# For now light entries don't have unload method, so we don't have to async_forward_entry_unload unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
# However we still have to cleanup the shared client! if unload_ok:
uuid = entry.data[CONF_ENTRY_ID] hass.data[DOMAIN].pop(entry.entry_id)
hass.data[DOMAIN].pop(uuid)
return True return unload_ok

View File

@ -14,16 +14,7 @@ from homeassistant.components import dhcp
from homeassistant.const import CONF_HOST from homeassistant.const import CONF_HOST
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import ( from .const import CONF_ID, CONF_MODEL, CONF_NAME, DEV_ID, DEV_MODEL, DEV_NAME, DOMAIN
CONF_ENTRY_HOST,
CONF_ENTRY_ID,
CONF_ENTRY_MODEL,
CONF_ENTRY_NAME,
DEV_ID,
DEV_MODEL,
DEV_NAME,
DOMAIN,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -49,16 +40,14 @@ class TwinklyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
device_info = await Twinkly( device_info = await Twinkly(
host, async_get_clientsession(self.hass) host, async_get_clientsession(self.hass)
).get_details() ).get_details()
except (asyncio.TimeoutError, ClientError):
errors[CONF_HOST] = "cannot_connect"
else:
await self.async_set_unique_id(device_info[DEV_ID]) await self.async_set_unique_id(device_info[DEV_ID])
self._abort_if_unique_id_configured() self._abort_if_unique_id_configured()
return self._create_entry_from_device(device_info, host) return self._create_entry_from_device(device_info, host)
except (asyncio.TimeoutError, ClientError) as err:
_LOGGER.info("Cannot reach Twinkly '%s' (client)", host, exc_info=err)
errors[CONF_HOST] = "cannot_connect"
return self.async_show_form( return self.async_show_form(
step_id="user", data_schema=Schema(schema), errors=errors step_id="user", data_schema=Schema(schema), errors=errors
) )
@ -67,14 +56,12 @@ class TwinklyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self, discovery_info: dhcp.DhcpServiceInfo self, discovery_info: dhcp.DhcpServiceInfo
) -> data_entry_flow.FlowResult: ) -> data_entry_flow.FlowResult:
"""Handle dhcp discovery for twinkly.""" """Handle dhcp discovery for twinkly."""
self._async_abort_entries_match({CONF_ENTRY_HOST: discovery_info.ip}) self._async_abort_entries_match({CONF_HOST: discovery_info.ip})
device_info = await Twinkly( device_info = await Twinkly(
discovery_info.ip, async_get_clientsession(self.hass) discovery_info.ip, async_get_clientsession(self.hass)
).get_details() ).get_details()
await self.async_set_unique_id(device_info[DEV_ID]) await self.async_set_unique_id(device_info[DEV_ID])
self._abort_if_unique_id_configured( self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.ip})
updates={CONF_ENTRY_HOST: discovery_info.ip}
)
self._discovered_device = (device_info, discovery_info.ip) self._discovered_device = (device_info, discovery_info.ip)
return await self.async_step_discovery_confirm() return await self.async_step_discovery_confirm()
@ -106,9 +93,9 @@ class TwinklyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_create_entry( return self.async_create_entry(
title=device_info[DEV_NAME], title=device_info[DEV_NAME],
data={ data={
CONF_ENTRY_HOST: host, CONF_HOST: host,
CONF_ENTRY_ID: device_info[DEV_ID], CONF_ID: device_info[DEV_ID],
CONF_ENTRY_NAME: device_info[DEV_NAME], CONF_NAME: device_info[DEV_NAME],
CONF_ENTRY_MODEL: device_info[DEV_MODEL], CONF_MODEL: device_info[DEV_MODEL],
}, },
) )

View File

@ -3,10 +3,10 @@
DOMAIN = "twinkly" DOMAIN = "twinkly"
# Keys of the config entry # Keys of the config entry
CONF_ENTRY_ID = "id" CONF_ID = "id"
CONF_ENTRY_HOST = "host" CONF_HOST = "host"
CONF_ENTRY_NAME = "name" CONF_NAME = "name"
CONF_ENTRY_MODEL = "model" CONF_MODEL = "model"
# Strongly named HA attributes keys # Strongly named HA attributes keys
ATTR_HOST = "host" ATTR_HOST = "host"

View File

@ -3,6 +3,7 @@ from __future__ import annotations
import asyncio import asyncio
import logging import logging
from typing import Any
from aiohttp import ClientError from aiohttp import ClientError
from ttls.client import Twinkly from ttls.client import Twinkly
@ -22,11 +23,10 @@ from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import ( from .const import (
ATTR_HOST, CONF_HOST,
CONF_ENTRY_HOST, CONF_ID,
CONF_ENTRY_ID, CONF_MODEL,
CONF_ENTRY_MODEL, CONF_NAME,
CONF_ENTRY_NAME,
DATA_CLIENT, DATA_CLIENT,
DATA_DEVICE_INFO, DATA_DEVICE_INFO,
DEV_LED_PROFILE, DEV_LED_PROFILE,
@ -48,8 +48,8 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Setups an entity from a config entry (UI config flow).""" """Setups an entity from a config entry (UI config flow)."""
client = hass.data[DOMAIN][config_entry.data[CONF_ENTRY_ID]][DATA_CLIENT] client = hass.data[DOMAIN][config_entry.entry_id][DATA_CLIENT]
device_info = hass.data[DOMAIN][config_entry.data[CONF_ENTRY_ID]][DATA_DEVICE_INFO] device_info = hass.data[DOMAIN][config_entry.entry_id][DATA_DEVICE_INFO]
entity = TwinklyLight(config_entry, client, device_info) entity = TwinklyLight(config_entry, client, device_info)
@ -66,7 +66,7 @@ class TwinklyLight(LightEntity):
device_info, device_info,
) -> None: ) -> None:
"""Initialize a TwinklyLight entity.""" """Initialize a TwinklyLight entity."""
self._id = conf.data[CONF_ENTRY_ID] self._id = conf.data[CONF_ID]
self._conf = conf self._conf = conf
if device_info.get(DEV_LED_PROFILE) == DEV_PROFILE_RGBW: if device_info.get(DEV_LED_PROFILE) == DEV_PROFILE_RGBW:
@ -84,20 +84,15 @@ class TwinklyLight(LightEntity):
# Those are saved in the config entry in order to have meaningful values even # Those are saved in the config entry in order to have meaningful values even
# if the device is currently offline. # if the device is currently offline.
# They are expected to be updated using the device_info. # They are expected to be updated using the device_info.
self.__name = conf.data[CONF_ENTRY_NAME] self._name = conf.data[CONF_NAME]
self.__model = conf.data[CONF_ENTRY_MODEL] self._model = conf.data[CONF_MODEL]
self._client = client self._client = client
# Set default state before any update # Set default state before any update
self._is_on = False self._is_on = False
self._is_available = False self._is_available = False
self._attributes = {ATTR_HOST: self._client.host} self._attributes: dict[Any, Any] = {}
@property
def should_poll(self) -> bool:
"""Get a boolean which indicates if this entity should be polled."""
return True
@property @property
def available(self) -> bool: def available(self) -> bool:
@ -112,12 +107,12 @@ class TwinklyLight(LightEntity):
@property @property
def name(self) -> str: def name(self) -> str:
"""Name of the device.""" """Name of the device."""
return self.__name if self.__name else "Twinkly light" return self._name if self._name else "Twinkly light"
@property @property
def model(self) -> str: def model(self) -> str:
"""Name of the device.""" """Name of the device."""
return self.__model return self._model
@property @property
def icon(self) -> str: def icon(self) -> str:
@ -127,15 +122,11 @@ class TwinklyLight(LightEntity):
@property @property
def device_info(self) -> DeviceInfo | None: def device_info(self) -> DeviceInfo | None:
"""Get device specific attributes.""" """Get device specific attributes."""
return ( return DeviceInfo(
DeviceInfo( identifiers={(DOMAIN, self._id)},
identifiers={(DOMAIN, self._id)}, manufacturer="LEDWORKS",
manufacturer="LEDWORKS", model=self.model,
model=self.model, name=self.name,
name=self.name,
)
if self._id
else None # device_info is available only for entities configured from the UI
) )
@property @property
@ -149,10 +140,6 @@ class TwinklyLight(LightEntity):
attributes = self._attributes attributes = self._attributes
# Make sure to update any normalized property
attributes[ATTR_HOST] = self._client.host
attributes[ATTR_BRIGHTNESS] = self._attr_brightness
return attributes return attributes
async def async_turn_on(self, **kwargs) -> None: async def async_turn_on(self, **kwargs) -> None:
@ -204,7 +191,7 @@ class TwinklyLight(LightEntity):
async def async_update(self) -> None: async def async_update(self) -> None:
"""Asynchronously updates the device properties.""" """Asynchronously updates the device properties."""
_LOGGER.info("Updating '%s'", self._client.host) _LOGGER.debug("Updating '%s'", self._client.host)
try: try:
self._is_on = await self._client.is_on() self._is_on = await self._client.is_on()
@ -224,25 +211,24 @@ class TwinklyLight(LightEntity):
DEV_NAME in device_info DEV_NAME in device_info
and DEV_MODEL in device_info and DEV_MODEL in device_info
and ( and (
device_info[DEV_NAME] != self.__name device_info[DEV_NAME] != self._name
or device_info[DEV_MODEL] != self.__model or device_info[DEV_MODEL] != self._model
) )
): ):
self.__name = device_info[DEV_NAME] self._name = device_info[DEV_NAME]
self.__model = device_info[DEV_MODEL] self._model = device_info[DEV_MODEL]
if self._conf is not None: # If the name has changed, persist it in conf entry,
# If the name has changed, persist it in conf entry, # so we will be able to restore this new name if hass is started while the LED string is offline.
# so we will be able to restore this new name if hass is started while the LED string is offline. self.hass.config_entries.async_update_entry(
self.hass.config_entries.async_update_entry( self._conf,
self._conf, data={
data={ CONF_HOST: self._client.host, # this cannot change
CONF_ENTRY_HOST: self._client.host, # this cannot change CONF_ID: self._id, # this cannot change
CONF_ENTRY_ID: self._id, # this cannot change CONF_NAME: self._name,
CONF_ENTRY_NAME: self.__name, CONF_MODEL: self._model,
CONF_ENTRY_MODEL: self.__model, },
}, )
)
for key, value in device_info.items(): for key, value in device_info.items():
if key not in HIDDEN_DEV_VALUES: if key not in HIDDEN_DEV_VALUES:

View File

@ -3,8 +3,7 @@
"name": "Twinkly", "name": "Twinkly",
"documentation": "https://www.home-assistant.io/integrations/twinkly", "documentation": "https://www.home-assistant.io/integrations/twinkly",
"requirements": ["ttls==1.4.2"], "requirements": ["ttls==1.4.2"],
"dependencies": [], "codeowners": ["@dr1rrb", "@Robbie1221"],
"codeowners": ["@dr1rrb"],
"config_flow": true, "config_flow": true,
"dhcp": [{ "hostname": "twinkly_*" }], "dhcp": [{ "hostname": "twinkly_*" }],
"iot_class": "local_polling" "iot_class": "local_polling"

View File

@ -5,7 +5,7 @@
"title": "Twinkly", "title": "Twinkly",
"description": "Set up your Twinkly led string", "description": "Set up your Twinkly led string",
"data": { "data": {
"host": "Host (or IP address) of your twinkly device" "host": "[%key:common::config_flow::data::host%]"
} }
}, },
"discovery_confirm": { "discovery_confirm": {

View File

@ -4,10 +4,10 @@ from unittest.mock import patch
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components import dhcp from homeassistant.components import dhcp
from homeassistant.components.twinkly.const import ( from homeassistant.components.twinkly.const import (
CONF_ENTRY_HOST, CONF_HOST,
CONF_ENTRY_ID, CONF_ID,
CONF_ENTRY_MODEL, CONF_MODEL,
CONF_ENTRY_NAME, CONF_NAME,
DOMAIN as TWINKLY_DOMAIN, DOMAIN as TWINKLY_DOMAIN,
) )
@ -31,12 +31,12 @@ async def test_invalid_host(hass):
assert result["errors"] == {} assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{CONF_ENTRY_HOST: "dummy"}, {CONF_HOST: "dummy"},
) )
assert result["type"] == "form" assert result["type"] == "form"
assert result["step_id"] == "user" assert result["step_id"] == "user"
assert result["errors"] == {CONF_ENTRY_HOST: "cannot_connect"} assert result["errors"] == {CONF_HOST: "cannot_connect"}
async def test_success_flow(hass): async def test_success_flow(hass):
@ -55,16 +55,16 @@ async def test_success_flow(hass):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{CONF_ENTRY_HOST: "dummy"}, {CONF_HOST: "dummy"},
) )
assert result["type"] == "create_entry" assert result["type"] == "create_entry"
assert result["title"] == client.id assert result["title"] == client.id
assert result["data"] == { assert result["data"] == {
CONF_ENTRY_HOST: "dummy", CONF_HOST: "dummy",
CONF_ENTRY_ID: client.id, CONF_ID: client.id,
CONF_ENTRY_NAME: client.id, CONF_NAME: client.id,
CONF_ENTRY_MODEL: TEST_MODEL, CONF_MODEL: TEST_MODEL,
} }
@ -114,10 +114,10 @@ async def test_dhcp_success(hass):
assert result["type"] == "create_entry" assert result["type"] == "create_entry"
assert result["title"] == client.id assert result["title"] == client.id
assert result["data"] == { assert result["data"] == {
CONF_ENTRY_HOST: "1.2.3.4", CONF_HOST: "1.2.3.4",
CONF_ENTRY_ID: client.id, CONF_ID: client.id,
CONF_ENTRY_NAME: client.id, CONF_NAME: client.id,
CONF_ENTRY_MODEL: TEST_MODEL, CONF_MODEL: TEST_MODEL,
} }
@ -128,10 +128,10 @@ async def test_dhcp_already_exists(hass):
entry = MockConfigEntry( entry = MockConfigEntry(
domain=TWINKLY_DOMAIN, domain=TWINKLY_DOMAIN,
data={ data={
CONF_ENTRY_HOST: "1.2.3.4", CONF_HOST: "1.2.3.4",
CONF_ENTRY_ID: client.id, CONF_ID: client.id,
CONF_ENTRY_NAME: client.id, CONF_NAME: client.id,
CONF_ENTRY_MODEL: TEST_MODEL, CONF_MODEL: TEST_MODEL,
}, },
unique_id=client.id, unique_id=client.id,
) )

View File

@ -3,12 +3,11 @@
from unittest.mock import patch from unittest.mock import patch
from uuid import uuid4 from uuid import uuid4
from homeassistant.components.twinkly import async_setup_entry, async_unload_entry
from homeassistant.components.twinkly.const import ( from homeassistant.components.twinkly.const import (
CONF_ENTRY_HOST, CONF_HOST,
CONF_ENTRY_ID, CONF_ID,
CONF_ENTRY_MODEL, CONF_MODEL,
CONF_ENTRY_NAME, CONF_NAME,
DOMAIN as TWINKLY_DOMAIN, DOMAIN as TWINKLY_DOMAIN,
) )
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
@ -23,7 +22,7 @@ from tests.components.twinkly import (
) )
async def test_setup_entry(hass: HomeAssistant): async def test_load_unload_entry(hass: HomeAssistant):
"""Validate that setup entry also configure the client.""" """Validate that setup entry also configure the client."""
client = ClientMock() client = ClientMock()
@ -31,47 +30,24 @@ async def test_setup_entry(hass: HomeAssistant):
config_entry = MockConfigEntry( config_entry = MockConfigEntry(
domain=TWINKLY_DOMAIN, domain=TWINKLY_DOMAIN,
data={ data={
CONF_ENTRY_HOST: TEST_HOST, CONF_HOST: TEST_HOST,
CONF_ENTRY_ID: id, CONF_ID: id,
CONF_ENTRY_NAME: TEST_NAME_ORIGINAL, CONF_NAME: TEST_NAME_ORIGINAL,
CONF_ENTRY_MODEL: TEST_MODEL, CONF_MODEL: TEST_MODEL,
}, },
entry_id=id, entry_id=id,
) )
def setup_mock(_, __): config_entry.add_to_hass(hass)
return True
with patch( with patch("homeassistant.components.twinkly.Twinkly", return_value=client):
"homeassistant.config_entries.ConfigEntries.async_forward_entry_setup", await hass.config_entries.async_setup(config_entry.entry_id)
side_effect=setup_mock,
), patch("homeassistant.components.twinkly.Twinkly", return_value=client):
await async_setup_entry(hass, config_entry)
assert hass.data[TWINKLY_DOMAIN][id] is not None assert config_entry.state == ConfigEntryState.LOADED
await hass.config_entries.async_unload(config_entry.entry_id)
async def test_unload_entry(hass: HomeAssistant): assert config_entry.state == ConfigEntryState.NOT_LOADED
"""Validate that unload entry also clear the client."""
id = str(uuid4())
config_entry = MockConfigEntry(
domain=TWINKLY_DOMAIN,
data={
CONF_ENTRY_HOST: TEST_HOST,
CONF_ENTRY_ID: id,
CONF_ENTRY_NAME: TEST_NAME_ORIGINAL,
CONF_ENTRY_MODEL: TEST_MODEL,
},
entry_id=id,
)
# Put random content at the location where the client should have been placed by setup
hass.data.setdefault(TWINKLY_DOMAIN, {})[id] = config_entry
await async_unload_entry(hass, config_entry)
assert hass.data[TWINKLY_DOMAIN].get(id) is None
async def test_config_entry_not_ready(hass: HomeAssistant): async def test_config_entry_not_ready(hass: HomeAssistant):
@ -82,10 +58,10 @@ async def test_config_entry_not_ready(hass: HomeAssistant):
config_entry = MockConfigEntry( config_entry = MockConfigEntry(
domain=TWINKLY_DOMAIN, domain=TWINKLY_DOMAIN,
data={ data={
CONF_ENTRY_HOST: TEST_HOST, CONF_HOST: TEST_HOST,
CONF_ENTRY_ID: id, CONF_ID: id,
CONF_ENTRY_NAME: TEST_NAME_ORIGINAL, CONF_NAME: TEST_NAME_ORIGINAL,
CONF_ENTRY_MODEL: TEST_MODEL, CONF_MODEL: TEST_MODEL,
}, },
) )

View File

@ -3,11 +3,12 @@ from __future__ import annotations
from unittest.mock import patch from unittest.mock import patch
from homeassistant.components.light import ATTR_BRIGHTNESS
from homeassistant.components.twinkly.const import ( from homeassistant.components.twinkly.const import (
CONF_ENTRY_HOST, CONF_HOST,
CONF_ENTRY_ID, CONF_ID,
CONF_ENTRY_MODEL, CONF_MODEL,
CONF_ENTRY_NAME, CONF_NAME,
DOMAIN as TWINKLY_DOMAIN, DOMAIN as TWINKLY_DOMAIN,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -16,25 +17,19 @@ from homeassistant.helpers.device_registry import DeviceEntry
from homeassistant.helpers.entity_registry import RegistryEntry from homeassistant.helpers.entity_registry import RegistryEntry
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.components.twinkly import ( from tests.components.twinkly import TEST_MODEL, TEST_NAME_ORIGINAL, ClientMock
TEST_HOST,
TEST_MODEL,
TEST_NAME_ORIGINAL,
ClientMock,
)
async def test_initial_state(hass: HomeAssistant): async def test_initial_state(hass: HomeAssistant):
"""Validate that entity and device states are updated on startup.""" """Validate that entity and device states are updated on startup."""
entity, device, _ = await _create_entries(hass) entity, device, _, _ = await _create_entries(hass)
state = hass.states.get(entity.entity_id) state = hass.states.get(entity.entity_id)
# Basic state properties # Basic state properties
assert state.name == entity.unique_id assert state.name == entity.unique_id
assert state.state == "on" assert state.state == "on"
assert state.attributes["host"] == TEST_HOST assert state.attributes[ATTR_BRIGHTNESS] == 26
assert state.attributes["brightness"] == 26
assert state.attributes["friendly_name"] == entity.unique_id assert state.attributes["friendly_name"] == entity.unique_id
assert state.attributes["icon"] == "mdi:string-lights" assert state.attributes["icon"] == "mdi:string-lights"
@ -54,7 +49,7 @@ async def test_turn_on_off(hass: HomeAssistant):
client = ClientMock() client = ClientMock()
client.state = False client.state = False
client.brightness = {"mode": "enabled", "value": 20} client.brightness = {"mode": "enabled", "value": 20}
entity, _, _ = await _create_entries(hass, client) entity, _, _, _ = await _create_entries(hass, client)
assert hass.states.get(entity.entity_id).state == "off" assert hass.states.get(entity.entity_id).state == "off"
@ -66,7 +61,7 @@ async def test_turn_on_off(hass: HomeAssistant):
state = hass.states.get(entity.entity_id) state = hass.states.get(entity.entity_id)
assert state.state == "on" assert state.state == "on"
assert state.attributes["brightness"] == 51 assert state.attributes[ATTR_BRIGHTNESS] == 51
async def test_turn_on_with_brightness(hass: HomeAssistant): async def test_turn_on_with_brightness(hass: HomeAssistant):
@ -74,7 +69,7 @@ async def test_turn_on_with_brightness(hass: HomeAssistant):
client = ClientMock() client = ClientMock()
client.state = False client.state = False
client.brightness = {"mode": "enabled", "value": 20} client.brightness = {"mode": "enabled", "value": 20}
entity, _, _ = await _create_entries(hass, client) entity, _, _, _ = await _create_entries(hass, client)
assert hass.states.get(entity.entity_id).state == "off" assert hass.states.get(entity.entity_id).state == "off"
@ -88,7 +83,7 @@ async def test_turn_on_with_brightness(hass: HomeAssistant):
state = hass.states.get(entity.entity_id) state = hass.states.get(entity.entity_id)
assert state.state == "on" assert state.state == "on"
assert state.attributes["brightness"] == 255 assert state.attributes[ATTR_BRIGHTNESS] == 255
await hass.services.async_call( await hass.services.async_call(
"light", "light",
@ -100,7 +95,6 @@ async def test_turn_on_with_brightness(hass: HomeAssistant):
state = hass.states.get(entity.entity_id) state = hass.states.get(entity.entity_id)
assert state.state == "off" assert state.state == "off"
assert state.attributes["brightness"] == 0
async def test_turn_on_with_color_rgbw(hass: HomeAssistant): async def test_turn_on_with_color_rgbw(hass: HomeAssistant):
@ -109,7 +103,7 @@ async def test_turn_on_with_color_rgbw(hass: HomeAssistant):
client.state = False client.state = False
client.device_info["led_profile"] = "RGBW" client.device_info["led_profile"] = "RGBW"
client.brightness = {"mode": "enabled", "value": 255} client.brightness = {"mode": "enabled", "value": 255}
entity, _, _ = await _create_entries(hass, client) entity, _, _, _ = await _create_entries(hass, client)
assert hass.states.get(entity.entity_id).state == "off" assert hass.states.get(entity.entity_id).state == "off"
@ -132,7 +126,7 @@ async def test_turn_on_with_color_rgb(hass: HomeAssistant):
client.state = False client.state = False
client.device_info["led_profile"] = "RGB" client.device_info["led_profile"] = "RGB"
client.brightness = {"mode": "enabled", "value": 255} client.brightness = {"mode": "enabled", "value": 255}
entity, _, _ = await _create_entries(hass, client) entity, _, _, _ = await _create_entries(hass, client)
assert hass.states.get(entity.entity_id).state == "off" assert hass.states.get(entity.entity_id).state == "off"
@ -151,7 +145,7 @@ async def test_turn_on_with_color_rgb(hass: HomeAssistant):
async def test_turn_off(hass: HomeAssistant): async def test_turn_off(hass: HomeAssistant):
"""Test support of the light.turn_off service.""" """Test support of the light.turn_off service."""
entity, _, _ = await _create_entries(hass) entity, _, _, _ = await _create_entries(hass)
assert hass.states.get(entity.entity_id).state == "on" assert hass.states.get(entity.entity_id).state == "on"
@ -163,7 +157,6 @@ async def test_turn_off(hass: HomeAssistant):
state = hass.states.get(entity.entity_id) state = hass.states.get(entity.entity_id)
assert state.state == "off" assert state.state == "off"
assert state.attributes["brightness"] == 0
async def test_update_name(hass: HomeAssistant): async def test_update_name(hass: HomeAssistant):
@ -174,15 +167,7 @@ async def test_update_name(hass: HomeAssistant):
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 = await _create_entries(hass) entity, _, client, config_entry = await _create_entries(hass)
updated_config_entry = None
async def on_update(ha, co):
nonlocal updated_config_entry
updated_config_entry = co
hass.config_entries.async_get_entry(entity.unique_id).add_update_listener(on_update)
client.change_name("new_device_name") client.change_name("new_device_name")
await hass.services.async_call( await hass.services.async_call(
@ -192,15 +177,14 @@ async def test_update_name(hass: HomeAssistant):
state = hass.states.get(entity.entity_id) state = hass.states.get(entity.entity_id)
assert updated_config_entry is not None assert config_entry.data[CONF_NAME] == "new_device_name"
assert updated_config_entry.data[CONF_ENTRY_NAME] == "new_device_name"
assert state.attributes["friendly_name"] == "new_device_name" assert state.attributes["friendly_name"] == "new_device_name"
async def test_unload(hass: HomeAssistant): async def test_unload(hass: HomeAssistant):
"""Validate that entities can be unloaded from the UI.""" """Validate that entities can be unloaded from the UI."""
_, _, client = await _create_entries(hass) _, _, client, _ = await _create_entries(hass)
entry_id = client.id entry_id = client.id
assert await hass.config_entries.async_unload(entry_id) assert await hass.config_entries.async_unload(entry_id)
@ -215,10 +199,10 @@ async def _create_entries(
config_entry = MockConfigEntry( config_entry = MockConfigEntry(
domain=TWINKLY_DOMAIN, domain=TWINKLY_DOMAIN,
data={ data={
CONF_ENTRY_HOST: client, CONF_HOST: client,
CONF_ENTRY_ID: client.id, CONF_ID: client.id,
CONF_ENTRY_NAME: TEST_NAME_ORIGINAL, CONF_NAME: TEST_NAME_ORIGINAL,
CONF_ENTRY_MODEL: TEST_MODEL, CONF_MODEL: TEST_MODEL,
}, },
entry_id=client.id, entry_id=client.id,
) )
@ -230,10 +214,10 @@ async def _create_entries(
entity_registry = er.async_get(hass) entity_registry = er.async_get(hass)
entity_id = entity_registry.async_get_entity_id("light", TWINKLY_DOMAIN, client.id) entity_id = entity_registry.async_get_entity_id("light", TWINKLY_DOMAIN, client.id)
entity = entity_registry.async_get(entity_id) entity_entry = entity_registry.async_get(entity_id)
device = device_registry.async_get_device({(TWINKLY_DOMAIN, client.id)}) device = device_registry.async_get_device({(TWINKLY_DOMAIN, client.id)})
assert entity is not None assert entity_entry is not None
assert device is not None assert device is not None
return entity, device, client return entity_entry, device, client, config_entry