1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-30 12:14:20 +01:00

Add tests to lutron (#162055)

Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
cdheiser
2026-02-24 13:30:31 -08:00
committed by GitHub
parent e514faf0bc
commit 28e8d7c3eb
16 changed files with 1395 additions and 1 deletions
+115 -1
View File
@@ -1,10 +1,15 @@
"""Provide common Lutron fixtures and mocks."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
from unittest.mock import AsyncMock, MagicMock, patch
from pylutron import OccupancyGroup
import pytest
from homeassistant.components.lutron.const import DOMAIN
from tests.common import MockConfigEntry
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
@@ -13,3 +18,112 @@ def mock_setup_entry() -> Generator[AsyncMock]:
"homeassistant.components.lutron.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry
@pytest.fixture
def mock_lutron() -> Generator[MagicMock]:
"""Mock Lutron client."""
with (
patch("homeassistant.components.lutron.Lutron", autospec=True) as mock_lutron,
patch("homeassistant.components.lutron.config_flow.Lutron", new=mock_lutron),
):
client = mock_lutron.return_value
client.guid = "12345678901"
client.areas = []
# Mock an area
area = MagicMock()
area.name = "Test Area"
area.outputs = []
area.keypads = []
area.occupancy_group = None
client.areas.append(area)
# Mock a light
light = MagicMock()
light.name = "Test Light"
light.id = "light_id"
light.uuid = "light_uuid"
light.legacy_uuid = "light_legacy_uuid"
light.is_dimmable = True
light.type = "LIGHT"
light.last_level.return_value = 0
area.outputs.append(light)
# Mock a switch
switch = MagicMock()
switch.name = "Test Switch"
switch.id = "switch_id"
switch.uuid = "switch_uuid"
switch.legacy_uuid = "switch_legacy_uuid"
switch.is_dimmable = False
switch.type = "NON_DIM"
switch.last_level.return_value = 0
area.outputs.append(switch)
# Mock a cover
cover = MagicMock()
cover.name = "Test Cover"
cover.id = "cover_id"
cover.uuid = "cover_uuid"
cover.legacy_uuid = "cover_legacy_uuid"
cover.type = "SYSTEM_SHADE"
cover.last_level.return_value = 0
area.outputs.append(cover)
# Mock a fan
fan = MagicMock()
fan.name = "Test Fan"
fan.uuid = "fan_uuid"
fan.legacy_uuid = "fan_legacy_uuid"
fan.type = "CEILING_FAN_TYPE"
fan.last_level.return_value = 0
area.outputs.append(fan)
# Mock a keypad with a button and LED
keypad = MagicMock()
keypad.name = "Test Keypad"
keypad.id = "keypad_id"
keypad.type = "KEYPAD"
area.keypads.append(keypad)
button = MagicMock()
button.name = "Test Button"
button.number = 1
button.button_type = "SingleAction"
button.uuid = "button_uuid"
button.legacy_uuid = "button_legacy_uuid"
keypad.buttons = [button]
led = MagicMock()
led.name = "Test LED"
led.number = 1
led.uuid = "led_uuid"
led.legacy_uuid = "led_legacy_uuid"
led.last_state = 0
keypad.leds = [led]
# Mock an occupancy group
occ_group = MagicMock()
occ_group.name = "Test Occupancy"
occ_group.id = "occ_id"
occ_group.uuid = "occ_uuid"
occ_group.legacy_uuid = "occ_legacy_uuid"
occ_group.state = OccupancyGroup.State.VACANT
area.occupancy_group = occ_group
yield client
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Mock a Lutron config entry."""
return MockConfigEntry(
domain=DOMAIN,
data={
"host": "127.0.0.1",
"username": "lutron",
"password": "password",
},
unique_id="12345678901",
)
@@ -0,0 +1,52 @@
# serializer version: 1
# name: test_binary_sensor_setup[binary_sensor.test_occupancy_occupancy-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': None,
'entity_id': 'binary_sensor.test_occupancy_occupancy',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Occupancy',
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.OCCUPANCY: 'occupancy'>,
'original_icon': None,
'original_name': 'Occupancy',
'platform': 'lutron',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '12345678901_occ_uuid',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensor_setup[binary_sensor.test_occupancy_occupancy-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'occupancy',
'friendly_name': 'Test Occupancy Occupancy',
'lutron_integration_id': 'occ_id',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.test_occupancy_occupancy',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
@@ -0,0 +1,53 @@
# serializer version: 1
# name: test_cover_setup[cover.test_cover-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'cover',
'entity_category': None,
'entity_id': 'cover.test_cover',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'lutron',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <CoverEntityFeature: 7>,
'translation_key': None,
'unique_id': '12345678901_cover_uuid',
'unit_of_measurement': None,
})
# ---
# name: test_cover_setup[cover.test_cover-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_position': 0,
'friendly_name': 'Test Cover',
'lutron_integration_id': 'cover_id',
'supported_features': <CoverEntityFeature: 7>,
}),
'context': <ANY>,
'entity_id': 'cover.test_cover',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'closed',
})
# ---
@@ -0,0 +1,58 @@
# serializer version: 1
# name: test_event_setup[event.test_keypad_test_button-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'event_types': list([
<LutronEventType.SINGLE_PRESS: 'single_press'>,
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'event',
'entity_category': None,
'entity_id': 'event.test_keypad_test_button',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Test Button',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Test Button',
'platform': 'lutron',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'button',
'unique_id': '12345678901_button_uuid',
'unit_of_measurement': None,
})
# ---
# name: test_event_setup[event.test_keypad_test_button-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'event_type': None,
'event_types': list([
<LutronEventType.SINGLE_PRESS: 'single_press'>,
]),
'friendly_name': 'Test Keypad Test Button',
}),
'context': <ANY>,
'entity_id': 'event.test_keypad_test_button',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
@@ -0,0 +1,57 @@
# serializer version: 1
# name: test_fan_setup[fan.test_fan-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'preset_modes': None,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'fan',
'entity_category': None,
'entity_id': 'fan.test_fan',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'lutron',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <FanEntityFeature: 49>,
'translation_key': None,
'unique_id': '12345678901_fan_uuid',
'unit_of_measurement': None,
})
# ---
# name: test_fan_setup[fan.test_fan-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Fan',
'percentage': 0,
'percentage_step': 33.333333333333336,
'preset_mode': None,
'preset_modes': None,
'supported_features': <FanEntityFeature: 49>,
}),
'context': <ANY>,
'entity_id': 'fan.test_fan',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
@@ -0,0 +1,61 @@
# serializer version: 1
# name: test_light_setup[light.test_light-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'supported_color_modes': list([
<ColorMode.BRIGHTNESS: 'brightness'>,
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'light',
'entity_category': None,
'entity_id': 'light.test_light',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'lutron',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <LightEntityFeature: 40>,
'translation_key': None,
'unique_id': '12345678901_light_uuid',
'unit_of_measurement': None,
})
# ---
# name: test_light_setup[light.test_light-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'brightness': None,
'color_mode': None,
'friendly_name': 'Test Light',
'lutron_integration_id': 'light_id',
'supported_color_modes': list([
<ColorMode.BRIGHTNESS: 'brightness'>,
]),
'supported_features': <LightEntityFeature: 40>,
}),
'context': <ANY>,
'entity_id': 'light.test_light',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
@@ -0,0 +1,50 @@
# serializer version: 1
# name: test_scene_setup[scene.test_keypad_test_button-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'scene',
'entity_category': None,
'entity_id': 'scene.test_keypad_test_button',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Test Button',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Test Button',
'platform': 'lutron',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '12345678901_button_uuid',
'unit_of_measurement': None,
})
# ---
# name: test_scene_setup[scene.test_keypad_test_button-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Keypad Test Button',
}),
'context': <ANY>,
'entity_id': 'scene.test_keypad_test_button',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
@@ -0,0 +1,103 @@
# serializer version: 1
# name: test_switch_setup[switch.test_keypad_test_button-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.test_keypad_test_button',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Test Button',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Test Button',
'platform': 'lutron',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '12345678901_led_uuid',
'unit_of_measurement': None,
})
# ---
# name: test_switch_setup[switch.test_keypad_test_button-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Keypad Test Button',
'keypad': 'Test Keypad',
'led': 'Test LED',
'scene': 'Test Button',
}),
'context': <ANY>,
'entity_id': 'switch.test_keypad_test_button',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch_setup[switch.test_switch-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.test_switch',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'lutron',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '12345678901_switch_uuid',
'unit_of_measurement': None,
})
# ---
# name: test_switch_setup[switch.test_switch-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Switch',
'lutron_integration_id': 'switch_id',
}),
'context': <ANY>,
'entity_id': 'switch.test_switch',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
@@ -0,0 +1,56 @@
"""Test Lutron binary sensor platform."""
from unittest.mock import MagicMock, patch
from pylutron import OccupancyGroup
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import STATE_OFF, STATE_ON, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, snapshot_platform
async def test_binary_sensor_setup(
hass: HomeAssistant,
mock_lutron: MagicMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test binary sensor setup."""
mock_config_entry.add_to_hass(hass)
occ_group = mock_lutron.areas[0].occupancy_group
occ_group.state = OccupancyGroup.State.VACANT
with patch("homeassistant.components.lutron.PLATFORMS", [Platform.BINARY_SENSOR]):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_binary_sensor_update(
hass: HomeAssistant, mock_lutron: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test binary sensor update."""
mock_config_entry.add_to_hass(hass)
occ_group = mock_lutron.areas[0].occupancy_group
occ_group.state = OccupancyGroup.State.VACANT
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
entity_id = "binary_sensor.test_occupancy_occupancy"
assert hass.states.get(entity_id).state == STATE_OFF
# Simulate update
occ_group.state = OccupancyGroup.State.OCCUPIED
callback = occ_group.subscribe.call_args[0][0]
callback(occ_group, None, None, None)
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == STATE_ON
+110
View File
@@ -0,0 +1,110 @@
"""Test Lutron cover platform."""
from unittest.mock import MagicMock, patch
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.cover import DOMAIN as COVER_DOMAIN
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_CLOSE_COVER,
SERVICE_OPEN_COVER,
SERVICE_SET_COVER_POSITION,
STATE_CLOSED,
STATE_OPEN,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, snapshot_platform
async def test_cover_setup(
hass: HomeAssistant,
mock_lutron: MagicMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test cover setup."""
mock_config_entry.add_to_hass(hass)
cover = mock_lutron.areas[0].outputs[2]
cover.level = 0
cover.last_level.return_value = 0
with patch("homeassistant.components.lutron.PLATFORMS", [Platform.COVER]):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_cover_services(
hass: HomeAssistant, mock_lutron: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test cover services."""
mock_config_entry.add_to_hass(hass)
cover = mock_lutron.areas[0].outputs[2]
cover.level = 0
cover.last_level.return_value = 0
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
entity_id = "cover.test_cover"
# Open cover
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
assert cover.level == 100
# Close cover
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
assert cover.level == 0
# Set cover position
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_POSITION,
{ATTR_ENTITY_ID: entity_id, "position": 50},
blocking=True,
)
assert cover.level == 50
async def test_cover_update(
hass: HomeAssistant, mock_lutron: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test cover state update."""
mock_config_entry.add_to_hass(hass)
cover = mock_lutron.areas[0].outputs[2]
cover.level = 0
cover.last_level.return_value = 0
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
entity_id = "cover.test_cover"
assert hass.states.get(entity_id).state == STATE_CLOSED
# Simulate update
cover.last_level.return_value = 100
callback = cover.subscribe.call_args[0][0]
callback(cover, None, None, None)
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == STATE_OPEN
assert hass.states.get(entity_id).attributes["current_position"] == 100
+88
View File
@@ -0,0 +1,88 @@
"""Test Lutron event platform."""
from unittest.mock import MagicMock, patch
from pylutron import Button
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, async_capture_events, snapshot_platform
async def test_event_setup(
hass: HomeAssistant,
mock_lutron: MagicMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test event setup."""
mock_config_entry.add_to_hass(hass)
with patch("homeassistant.components.lutron.PLATFORMS", [Platform.EVENT]):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_event_single_press(
hass: HomeAssistant, mock_lutron: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test single press event."""
mock_config_entry.add_to_hass(hass)
button = mock_lutron.areas[0].keypads[0].buttons[0]
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
# Subscribe to events
events = async_capture_events(hass, "lutron_event")
# Simulate button press
for call in button.subscribe.call_args_list:
callback = call[0][0]
callback(button, None, Button.Event.PRESSED, None)
await hass.async_block_till_done()
# Check bus event
assert len(events) == 1
assert events[0].data["action"] == "single"
assert events[0].data["uuid"] == "button_uuid"
async def test_event_press_release(
hass: HomeAssistant, mock_lutron: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test press and release events."""
mock_config_entry.add_to_hass(hass)
button = mock_lutron.areas[0].keypads[0].buttons[0]
button.button_type = "MasterRaiseLower"
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
# Subscribe to events
events = async_capture_events(hass, "lutron_event")
# Simulate button press
for call in button.subscribe.call_args_list:
callback = call[0][0]
callback(button, None, Button.Event.PRESSED, None)
await hass.async_block_till_done()
assert len(events) == 1
assert events[0].data["action"] == "pressed"
# Simulate button release
for call in button.subscribe.call_args_list:
callback = call[0][0]
callback(button, None, Button.Event.RELEASED, None)
await hass.async_block_till_done()
assert len(events) == 2
assert events[1].data["action"] == "released"
+108
View File
@@ -0,0 +1,108 @@
"""Test Lutron fan platform."""
from unittest.mock import MagicMock, patch
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.fan import (
ATTR_PERCENTAGE,
DOMAIN as FAN_DOMAIN,
SERVICE_SET_PERCENTAGE,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
)
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, snapshot_platform
async def test_fan_setup(
hass: HomeAssistant,
mock_lutron: MagicMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test fan setup."""
mock_config_entry.add_to_hass(hass)
fan = mock_lutron.areas[0].outputs[3]
fan.level = 0
fan.last_level.return_value = 0
with patch("homeassistant.components.lutron.PLATFORMS", [Platform.FAN]):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_fan_services(
hass: HomeAssistant, mock_lutron: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test fan services."""
mock_config_entry.add_to_hass(hass)
fan = mock_lutron.areas[0].outputs[3]
fan.level = 0
fan.last_level.return_value = 0
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
entity_id = "fan.test_fan"
# Turn on (defaults to medium - 67%)
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
assert fan.level == 67
# Turn off
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
assert fan.level == 0
# Set percentage
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_SET_PERCENTAGE,
{ATTR_ENTITY_ID: entity_id, ATTR_PERCENTAGE: 33},
blocking=True,
)
assert fan.level == 33
async def test_fan_update(
hass: HomeAssistant, mock_lutron: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test fan state update."""
mock_config_entry.add_to_hass(hass)
fan = mock_lutron.areas[0].outputs[3]
fan.level = 0
fan.last_level.return_value = 0
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
entity_id = "fan.test_fan"
assert hass.states.get(entity_id).state == STATE_OFF
# Simulate update
fan.last_level.return_value = 100
callback = fan.subscribe.call_args[0][0]
callback(fan, None, None, None)
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == STATE_ON
assert hass.states.get(entity_id).attributes[ATTR_PERCENTAGE] == 100
+101
View File
@@ -0,0 +1,101 @@
"""Test Lutron integration setup."""
from unittest.mock import MagicMock
from homeassistant.components.lutron.const import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
async def test_setup_entry(
hass: HomeAssistant, mock_lutron: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test setting up the integration."""
mock_config_entry.add_to_hass(hass)
assert await async_setup_component(hass, "lutron", {})
await hass.async_block_till_done()
assert mock_config_entry.runtime_data.client is mock_lutron
assert len(mock_config_entry.runtime_data.lights) == 1
# Verify that the unique ID is generated correctly.
# This prevents regression in unique ID generation which would be a breaking change.
entity_registry = er.async_get(hass)
# The light from mock_lutron has uuid="light_uuid" and guid="12345678901"
expected_unique_id = "12345678901_light_uuid"
entry = entity_registry.async_get("light.test_light")
assert entry.unique_id == expected_unique_id
async def test_unload_entry(
hass: HomeAssistant, mock_lutron: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test unloading the integration."""
mock_config_entry.add_to_hass(hass)
assert await async_setup_component(hass, "lutron", {})
await hass.async_block_till_done()
assert await hass.config_entries.async_unload(mock_config_entry.entry_id)
await hass.async_block_till_done()
async def test_unique_id_migration(
hass: HomeAssistant, mock_lutron: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test migration of legacy unique IDs to the newer UUID-based format.
In older versions of the integration, unique IDs were based on a legacy UUID format.
The integration now prefers a newer UUID format when available. This test ensures
that existing entities and devices are automatically migrated to the new format
without losing their registry entries.
"""
mock_config_entry.add_to_hass(hass)
# Setup registries with an entry using the "legacy" unique ID format.
# This simulates a user who had configured the integration in an older version.
entity_registry = er.async_get(hass)
device_registry = dr.async_get(hass)
legacy_unique_id = "12345678901_light_legacy_uuid"
new_unique_id = "12345678901_light_uuid"
# Create a device in the registry using the legacy ID
device = device_registry.async_get_or_create(
config_entry_id=mock_config_entry.entry_id,
identifiers={(DOMAIN, legacy_unique_id)},
manufacturer="Lutron",
name="Test Light",
)
# Create an entity in the registry using the legacy ID
entity = entity_registry.async_get_or_create(
domain="light",
platform="lutron",
unique_id=legacy_unique_id,
config_entry=mock_config_entry,
device_id=device.id,
)
# Verify our starting state: registry holds the legacy ID
assert entity.unique_id == legacy_unique_id
assert (DOMAIN, legacy_unique_id) in device.identifiers
# Trigger the integration setup.
# The async_setup_entry logic will detect the legacy IDs in the registry
# and update them to the new UUIDs provided by the mock_lutron fixture.
assert await async_setup_component(hass, "lutron", {})
await hass.async_block_till_done()
# Verify that the entity's unique ID has been updated to the new format.
entity = entity_registry.async_get(entity.entity_id)
assert entity.unique_id == new_unique_id
# Verify that the device's identifiers have also been migrated.
device = device_registry.async_get(device.id)
assert (DOMAIN, new_unique_id) in device.identifiers
assert (DOMAIN, legacy_unique_id) not in device.identifiers
+222
View File
@@ -0,0 +1,222 @@
"""Test Lutron light platform."""
from unittest.mock import MagicMock, patch
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_FLASH,
ATTR_TRANSITION,
DOMAIN as LIGHT_DOMAIN,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_OFF,
STATE_ON,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, snapshot_platform
async def test_light_setup(
hass: HomeAssistant,
mock_lutron: MagicMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test light setup."""
mock_config_entry.add_to_hass(hass)
light = mock_lutron.areas[0].outputs[0]
light.level = 0
light.last_level.return_value = 0
with patch("homeassistant.components.lutron.PLATFORMS", [Platform.LIGHT]):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_light_turn_on_off(
hass: HomeAssistant, mock_lutron: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test light turn on and off."""
mock_config_entry.add_to_hass(hass)
light = mock_lutron.areas[0].outputs[0]
light.level = 0
light.last_level.return_value = 0
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
entity_id = "light.test_light"
# Turn on
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 128},
blocking=True,
)
light.set_level.assert_called_with(new_level=pytest.approx(50.196, rel=1e-3))
# Turn off
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
light.set_level.assert_called_with(new_level=0)
async def test_light_update(
hass: HomeAssistant, mock_lutron: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test light state update from library."""
mock_config_entry.add_to_hass(hass)
light = mock_lutron.areas[0].outputs[0]
light.level = 0
light.last_level.return_value = 0
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
entity_id = "light.test_light"
assert hass.states.get(entity_id).state == STATE_OFF
# Simulate update from library
light.last_level.return_value = 100
# The library calls the callback registered with subscribe
callback = light.subscribe.call_args[0][0]
callback(light, None, None, None)
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == STATE_ON
assert hass.states.get(entity_id).attributes[ATTR_BRIGHTNESS] == 255
async def test_light_transition(
hass: HomeAssistant, mock_lutron: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test light turn on/off with transition."""
mock_config_entry.add_to_hass(hass)
light = mock_lutron.areas[0].outputs[0]
light.level = 0
light.last_level.return_value = 0
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
entity_id = "light.test_light"
# Turn on with transition
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id, ATTR_TRANSITION: 2.5},
blocking=True,
)
# Default brightness is used if not specified (DEFAULT_DIMMER_LEVEL is 50%)
light.set_level.assert_called_with(
new_level=pytest.approx(50.0, abs=0.5), fade_time_seconds=2.5
)
# Turn off with transition
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: entity_id, ATTR_TRANSITION: 3.0},
blocking=True,
)
light.set_level.assert_called_with(new_level=0, fade_time_seconds=3.0)
async def test_light_flash(
hass: HomeAssistant, mock_lutron: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test light flash."""
mock_config_entry.add_to_hass(hass)
light = mock_lutron.areas[0].outputs[0]
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
entity_id = "light.test_light"
# Short flash
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id, ATTR_FLASH: "short"},
blocking=True,
)
light.flash.assert_called_with(0.5)
# Long flash
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id, ATTR_FLASH: "long"},
blocking=True,
)
light.flash.assert_called_with(1.5)
async def test_light_brightness_restore(
hass: HomeAssistant, mock_lutron: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test light brightness restore logic."""
mock_config_entry.add_to_hass(hass)
light = mock_lutron.areas[0].outputs[0]
light.level = 0
light.last_level.return_value = 0
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
entity_id = "light.test_light"
# Turn on first time - uses default (50%)
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
light.set_level.assert_called_with(new_level=pytest.approx(50.0, abs=0.5))
# Simulate update to 50% (Lutron level 50 -> HA level 127)
light.last_level.return_value = 50
callback = light.subscribe.call_args[0][0]
callback(light, None, None, None)
await hass.async_block_till_done()
# Turn off
light.last_level.return_value = 0
callback(light, None, None, None)
await hass.async_block_till_done()
# Turn on again - should restore ~50%
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
# HA level 127 -> Lutron level ~49.8
light.set_level.assert_called_with(new_level=pytest.approx(50.0, abs=0.5))
+51
View File
@@ -0,0 +1,51 @@
"""Test Lutron scene platform."""
from unittest.mock import MagicMock, patch
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, snapshot_platform
async def test_scene_setup(
hass: HomeAssistant,
mock_lutron: MagicMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test scene setup."""
mock_config_entry.add_to_hass(hass)
with patch("homeassistant.components.lutron.PLATFORMS", [Platform.SCENE]):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_scene_activate(
hass: HomeAssistant, mock_lutron: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test scene activation."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
entity_id = "scene.test_keypad_test_button"
button = mock_lutron.areas[0].keypads[0].buttons[0]
await hass.services.async_call(
SCENE_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
button.tap.assert_called_once()
+110
View File
@@ -0,0 +1,110 @@
"""Test Lutron switch platform."""
from unittest.mock import MagicMock, patch
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, snapshot_platform
async def test_switch_setup(
hass: HomeAssistant,
mock_lutron: MagicMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test switch setup."""
mock_config_entry.add_to_hass(hass)
switch = mock_lutron.areas[0].outputs[1]
switch.level = 0
switch.last_level.return_value = 0
led = mock_lutron.areas[0].keypads[0].leds[0]
led.state = 0
led.last_state = 0
with patch("homeassistant.components.lutron.PLATFORMS", [Platform.SWITCH]):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_switch_turn_on_off(
hass: HomeAssistant, mock_lutron: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test switch turn on and off."""
mock_config_entry.add_to_hass(hass)
switch = mock_lutron.areas[0].outputs[1]
switch.level = 0
switch.last_level.return_value = 0
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
entity_id = "switch.test_switch"
# Turn on
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
assert switch.level == 100
# Turn off
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
assert switch.level == 0
async def test_led_turn_on_off(
hass: HomeAssistant, mock_lutron: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test LED turn on and off."""
mock_config_entry.add_to_hass(hass)
led = mock_lutron.areas[0].keypads[0].leds[0]
led.state = 0
led.last_state = 0
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
entity_id = "switch.test_keypad_test_button"
# Turn on
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
assert led.state == 1
# Turn off
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
assert led.state == 0