core/tests/components/kulersky/test_light.py

316 lines
9.9 KiB
Python

"""Test the Kuler Sky lights."""
import asyncio
from unittest.mock import MagicMock, patch
import pykulersky
import pytest
from homeassistant import setup
from homeassistant.components.kulersky.light import DOMAIN
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_HS_COLOR,
ATTR_RGB_COLOR,
ATTR_WHITE_VALUE,
ATTR_XY_COLOR,
SCAN_INTERVAL,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_WHITE_VALUE,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_FRIENDLY_NAME,
ATTR_SUPPORTED_FEATURES,
STATE_OFF,
STATE_ON,
STATE_UNAVAILABLE,
)
import homeassistant.util.dt as dt_util
from tests.common import MockConfigEntry, async_fire_time_changed
@pytest.fixture
async def mock_entry(hass):
"""Create a mock light entity."""
return MockConfigEntry(domain=DOMAIN)
@pytest.fixture
async def mock_light(hass, mock_entry):
"""Create a mock light entity."""
await setup.async_setup_component(hass, "persistent_notification", {})
light = MagicMock(spec=pykulersky.Light)
light.address = "AA:BB:CC:11:22:33"
light.name = "Bedroom"
light.connected = False
with patch(
"homeassistant.components.kulersky.light.pykulersky.discover_bluetooth_devices",
return_value=[
{
"address": "AA:BB:CC:11:22:33",
"name": "Bedroom",
}
],
):
with patch(
"homeassistant.components.kulersky.light.pykulersky.Light",
return_value=light,
), patch.object(light, "connect") as mock_connect, patch.object(
light, "get_color", return_value=(0, 0, 0, 0)
):
mock_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
assert mock_connect.called
light.connected = True
yield light
async def test_init(hass, mock_light):
"""Test platform setup."""
state = hass.states.get("light.bedroom")
assert state.state == STATE_OFF
assert state.attributes == {
ATTR_FRIENDLY_NAME: "Bedroom",
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS
| SUPPORT_COLOR
| SUPPORT_WHITE_VALUE,
}
with patch.object(hass.loop, "stop"), patch.object(
mock_light, "disconnect"
) as mock_disconnect:
await hass.async_stop()
await hass.async_block_till_done()
assert mock_disconnect.called
async def test_discovery_lock(hass, mock_entry):
"""Test discovery lock."""
await setup.async_setup_component(hass, "persistent_notification", {})
discovery_finished = None
first_discovery_started = asyncio.Event()
async def mock_discovery(*args):
"""Block to simulate multiple discovery calls while one still running."""
nonlocal discovery_finished
if discovery_finished:
first_discovery_started.set()
await discovery_finished.wait()
return []
with patch(
"homeassistant.components.kulersky.light.pykulersky.discover_bluetooth_devices",
return_value=[],
), patch(
"homeassistant.components.kulersky.light.async_track_time_interval",
) as mock_track_time_interval:
mock_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
with patch.object(
hass, "async_add_executor_job", side_effect=mock_discovery
) as mock_run_discovery:
discovery_coroutine = mock_track_time_interval.call_args[0][1]
discovery_finished = asyncio.Event()
# Schedule multiple discoveries
hass.async_create_task(discovery_coroutine())
hass.async_create_task(discovery_coroutine())
hass.async_create_task(discovery_coroutine())
# Wait until the first discovery call is blocked
await first_discovery_started.wait()
# Unblock the first discovery
discovery_finished.set()
# Flush the remaining jobs
await hass.async_block_till_done()
# The discovery method should only have been called once
mock_run_discovery.assert_called_once()
async def test_discovery_connection_error(hass, mock_entry):
"""Test that invalid devices are skipped."""
await setup.async_setup_component(hass, "persistent_notification", {})
light = MagicMock(spec=pykulersky.Light)
light.address = "AA:BB:CC:11:22:33"
light.name = "Bedroom"
light.connected = False
with patch(
"homeassistant.components.kulersky.light.pykulersky.discover_bluetooth_devices",
return_value=[
{
"address": "AA:BB:CC:11:22:33",
"name": "Bedroom",
}
],
):
with patch(
"homeassistant.components.kulersky.light.pykulersky.Light"
) as mockdevice, patch.object(
light, "connect", side_effect=pykulersky.PykulerskyException
):
mockdevice.return_value = light
mock_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
# Assert entity was not added
state = hass.states.get("light.bedroom")
assert state is None
async def test_remove_entry(hass, mock_light, mock_entry):
"""Test platform setup."""
with patch.object(mock_light, "disconnect") as mock_disconnect:
await hass.config_entries.async_remove(mock_entry.entry_id)
assert mock_disconnect.called
async def test_update_exception(hass, mock_light):
"""Test platform setup."""
await setup.async_setup_component(hass, "persistent_notification", {})
with patch.object(
mock_light, "get_color", side_effect=pykulersky.PykulerskyException
):
await hass.helpers.entity_component.async_update_entity("light.bedroom")
state = hass.states.get("light.bedroom")
assert state is not None
assert state.state == STATE_UNAVAILABLE
async def test_light_turn_on(hass, mock_light):
"""Test KulerSkyLight turn_on."""
with patch.object(mock_light, "set_color") as mock_set_color, patch.object(
mock_light, "get_color", return_value=(255, 255, 255, 255)
):
await hass.services.async_call(
"light",
"turn_on",
{ATTR_ENTITY_ID: "light.bedroom"},
blocking=True,
)
await hass.async_block_till_done()
mock_set_color.assert_called_with(255, 255, 255, 255)
with patch.object(mock_light, "set_color") as mock_set_color, patch.object(
mock_light, "get_color", return_value=(50, 50, 50, 255)
):
await hass.services.async_call(
"light",
"turn_on",
{ATTR_ENTITY_ID: "light.bedroom", ATTR_BRIGHTNESS: 50},
blocking=True,
)
await hass.async_block_till_done()
mock_set_color.assert_called_with(50, 50, 50, 255)
with patch.object(mock_light, "set_color") as mock_set_color, patch.object(
mock_light, "get_color", return_value=(50, 45, 25, 255)
):
await hass.services.async_call(
"light",
"turn_on",
{ATTR_ENTITY_ID: "light.bedroom", ATTR_HS_COLOR: (50, 50)},
blocking=True,
)
await hass.async_block_till_done()
mock_set_color.assert_called_with(50, 45, 25, 255)
with patch.object(mock_light, "set_color") as mock_set_color, patch.object(
mock_light, "get_color", return_value=(220, 201, 110, 180)
):
await hass.services.async_call(
"light",
"turn_on",
{ATTR_ENTITY_ID: "light.bedroom", ATTR_WHITE_VALUE: 180},
blocking=True,
)
await hass.async_block_till_done()
mock_set_color.assert_called_with(50, 45, 25, 180)
async def test_light_turn_off(hass, mock_light):
"""Test KulerSkyLight turn_on."""
with patch.object(mock_light, "set_color") as mock_set_color, patch.object(
mock_light, "get_color", return_value=(0, 0, 0, 0)
):
await hass.services.async_call(
"light",
"turn_off",
{ATTR_ENTITY_ID: "light.bedroom"},
blocking=True,
)
await hass.async_block_till_done()
mock_set_color.assert_called_with(0, 0, 0, 0)
async def test_light_update(hass, mock_light):
"""Test KulerSkyLight update."""
utcnow = dt_util.utcnow()
state = hass.states.get("light.bedroom")
assert state.state == STATE_OFF
assert state.attributes == {
ATTR_FRIENDLY_NAME: "Bedroom",
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS
| SUPPORT_COLOR
| SUPPORT_WHITE_VALUE,
}
# Test an exception during discovery
with patch.object(
mock_light, "get_color", side_effect=pykulersky.PykulerskyException("TEST")
):
utcnow = utcnow + SCAN_INTERVAL
async_fire_time_changed(hass, utcnow)
await hass.async_block_till_done()
state = hass.states.get("light.bedroom")
assert state.state == STATE_UNAVAILABLE
assert state.attributes == {
ATTR_FRIENDLY_NAME: "Bedroom",
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS
| SUPPORT_COLOR
| SUPPORT_WHITE_VALUE,
}
with patch.object(
mock_light,
"get_color",
return_value=(80, 160, 200, 240),
):
utcnow = utcnow + SCAN_INTERVAL
async_fire_time_changed(hass, utcnow)
await hass.async_block_till_done()
state = hass.states.get("light.bedroom")
assert state.state == STATE_ON
assert state.attributes == {
ATTR_FRIENDLY_NAME: "Bedroom",
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS
| SUPPORT_COLOR
| SUPPORT_WHITE_VALUE,
ATTR_BRIGHTNESS: 200,
ATTR_HS_COLOR: (200, 60),
ATTR_RGB_COLOR: (102, 203, 255),
ATTR_WHITE_VALUE: 240,
ATTR_XY_COLOR: (0.184, 0.261),
}