mirror of
https://github.com/home-assistant/core.git
synced 2026-04-02 08:26:41 +01:00
304 lines
10 KiB
Python
304 lines
10 KiB
Python
"""Tests for the Lutron Caseta integration."""
|
|
|
|
from typing import Any
|
|
from unittest.mock import AsyncMock
|
|
|
|
import pytest
|
|
|
|
from homeassistant.components.cover import (
|
|
DOMAIN as COVER_DOMAIN,
|
|
SERVICE_CLOSE_COVER,
|
|
SERVICE_OPEN_COVER,
|
|
SERVICE_STOP_COVER,
|
|
)
|
|
from homeassistant.const import ATTR_ENTITY_ID
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers import entity_registry as er
|
|
|
|
from . import MockBridge, async_setup_integration
|
|
|
|
|
|
@pytest.fixture
|
|
async def mock_bridge_with_cover_mocks(hass: HomeAssistant) -> MockBridge:
|
|
"""Set up mock bridge with all cover methods mocked for testing."""
|
|
instance = MockBridge()
|
|
|
|
def factory(*args: Any, **kwargs: Any) -> MockBridge:
|
|
"""Return the mock bridge instance."""
|
|
return instance
|
|
|
|
# Patch all cover methods on the instance with AsyncMocks
|
|
instance.set_value = AsyncMock()
|
|
instance.raise_cover = AsyncMock()
|
|
instance.lower_cover = AsyncMock()
|
|
instance.stop_cover = AsyncMock()
|
|
|
|
await async_setup_integration(hass, factory)
|
|
await hass.async_block_till_done()
|
|
|
|
return instance
|
|
|
|
|
|
async def test_cover_unique_id(
|
|
hass: HomeAssistant, entity_registry: er.EntityRegistry
|
|
) -> None:
|
|
"""Test a cover unique ID."""
|
|
await async_setup_integration(hass, MockBridge)
|
|
|
|
cover_entity_id = "cover.basement_bedroom_left_shade"
|
|
|
|
# Assert that Caseta covers will have the bridge serial hash and the zone id as the uniqueID
|
|
assert entity_registry.async_get(cover_entity_id).unique_id == "000004d2_802"
|
|
|
|
|
|
async def test_cover_open_close_using_set_value(
|
|
hass: HomeAssistant, mock_bridge_with_cover_mocks: MockBridge
|
|
) -> None:
|
|
"""Test that open/close commands use set_value to avoid stuttering."""
|
|
mock_instance = mock_bridge_with_cover_mocks
|
|
cover_entity_id = "cover.basement_bedroom_left_shade"
|
|
|
|
# Test opening the cover
|
|
await hass.services.async_call(
|
|
COVER_DOMAIN,
|
|
SERVICE_OPEN_COVER,
|
|
{ATTR_ENTITY_ID: cover_entity_id},
|
|
blocking=True,
|
|
)
|
|
|
|
# Should use set_value(100) instead of raise_cover
|
|
mock_instance.set_value.assert_called_with("802", 100)
|
|
mock_instance.raise_cover.assert_not_called()
|
|
|
|
mock_instance.set_value.reset_mock()
|
|
mock_instance.lower_cover.reset_mock()
|
|
|
|
# Test closing the cover
|
|
await hass.services.async_call(
|
|
COVER_DOMAIN,
|
|
SERVICE_CLOSE_COVER,
|
|
{ATTR_ENTITY_ID: cover_entity_id},
|
|
blocking=True,
|
|
)
|
|
|
|
# Should use set_value(0) instead of lower_cover
|
|
mock_instance.set_value.assert_called_with("802", 0)
|
|
mock_instance.lower_cover.assert_not_called()
|
|
|
|
|
|
async def test_cover_stop_with_direction_tracking(
|
|
hass: HomeAssistant, mock_bridge_with_cover_mocks: MockBridge
|
|
) -> None:
|
|
"""Test that stop command sends appropriate directional command first."""
|
|
mock_instance = mock_bridge_with_cover_mocks
|
|
cover_entity_id = "cover.basement_bedroom_left_shade"
|
|
|
|
# Simulate shade moving up (opening)
|
|
mock_instance.devices["802"]["current_state"] = 30
|
|
mock_instance.call_subscribers("802")
|
|
await hass.async_block_till_done()
|
|
|
|
mock_instance.devices["802"]["current_state"] = 60
|
|
mock_instance.call_subscribers("802")
|
|
await hass.async_block_till_done()
|
|
|
|
# Now stop while opening
|
|
await hass.services.async_call(
|
|
COVER_DOMAIN,
|
|
SERVICE_STOP_COVER,
|
|
{ATTR_ENTITY_ID: cover_entity_id},
|
|
blocking=True,
|
|
)
|
|
|
|
# Should send raise_cover before stop_cover when opening
|
|
mock_instance.raise_cover.assert_called_with("802")
|
|
mock_instance.stop_cover.assert_called_with("802")
|
|
mock_instance.lower_cover.assert_not_called()
|
|
|
|
mock_instance.raise_cover.reset_mock()
|
|
mock_instance.lower_cover.reset_mock()
|
|
mock_instance.stop_cover.reset_mock()
|
|
|
|
# Simulate shade moving down (closing)
|
|
mock_instance.devices["802"]["current_state"] = 40
|
|
mock_instance.call_subscribers("802")
|
|
await hass.async_block_till_done()
|
|
|
|
mock_instance.devices["802"]["current_state"] = 20
|
|
mock_instance.call_subscribers("802")
|
|
await hass.async_block_till_done()
|
|
|
|
# Now stop while closing
|
|
await hass.services.async_call(
|
|
COVER_DOMAIN,
|
|
SERVICE_STOP_COVER,
|
|
{ATTR_ENTITY_ID: cover_entity_id},
|
|
blocking=True,
|
|
)
|
|
|
|
# Should send lower_cover before stop_cover when closing
|
|
mock_instance.lower_cover.assert_called_with("802")
|
|
mock_instance.stop_cover.assert_called_with("802")
|
|
mock_instance.raise_cover.assert_not_called()
|
|
|
|
|
|
async def test_cover_stop_at_endpoints(
|
|
hass: HomeAssistant, mock_bridge_with_cover_mocks: MockBridge
|
|
) -> None:
|
|
"""Test stop command behavior when shade is at fully open or closed."""
|
|
mock_instance = mock_bridge_with_cover_mocks
|
|
cover_entity_id = "cover.basement_bedroom_left_shade"
|
|
|
|
# Test stop at fully open (100) - should infer it was opening
|
|
mock_instance.devices["802"]["current_state"] = 100
|
|
mock_instance.call_subscribers("802")
|
|
await hass.async_block_till_done()
|
|
|
|
await hass.services.async_call(
|
|
COVER_DOMAIN,
|
|
SERVICE_STOP_COVER,
|
|
{ATTR_ENTITY_ID: cover_entity_id},
|
|
blocking=True,
|
|
)
|
|
|
|
# At fully open, should send raise_cover before stop
|
|
mock_instance.raise_cover.assert_called_with("802")
|
|
mock_instance.stop_cover.assert_called_with("802")
|
|
|
|
mock_instance.raise_cover.reset_mock()
|
|
mock_instance.lower_cover.reset_mock()
|
|
mock_instance.stop_cover.reset_mock()
|
|
|
|
# Test stop at fully closed (0) - should infer it was closing
|
|
mock_instance.devices["802"]["current_state"] = 0
|
|
mock_instance.call_subscribers("802")
|
|
await hass.async_block_till_done()
|
|
|
|
await hass.services.async_call(
|
|
COVER_DOMAIN,
|
|
SERVICE_STOP_COVER,
|
|
{ATTR_ENTITY_ID: cover_entity_id},
|
|
blocking=True,
|
|
)
|
|
|
|
# At fully closed, should send lower_cover before stop
|
|
mock_instance.lower_cover.assert_called_with("802")
|
|
mock_instance.stop_cover.assert_called_with("802")
|
|
|
|
|
|
async def test_cover_position_heuristic_fallback(
|
|
hass: HomeAssistant, mock_bridge_with_cover_mocks: MockBridge
|
|
) -> None:
|
|
"""Test stop command uses position heuristic when movement direction is unknown."""
|
|
mock_instance = mock_bridge_with_cover_mocks
|
|
cover_entity_id = "cover.basement_bedroom_left_shade"
|
|
|
|
# Test stop at position < 50 with no movement
|
|
# Update the device data directly in the bridge's devices dict
|
|
mock_instance.devices["802"]["current_state"] = 30
|
|
mock_instance.call_subscribers("802")
|
|
await hass.async_block_till_done()
|
|
|
|
await hass.services.async_call(
|
|
COVER_DOMAIN,
|
|
SERVICE_STOP_COVER,
|
|
{ATTR_ENTITY_ID: cover_entity_id},
|
|
blocking=True,
|
|
)
|
|
|
|
# Position < 50, should send lower_cover
|
|
mock_instance.lower_cover.assert_called_with("802")
|
|
mock_instance.stop_cover.assert_called_with("802")
|
|
|
|
mock_instance.raise_cover.reset_mock()
|
|
mock_instance.lower_cover.reset_mock()
|
|
mock_instance.stop_cover.reset_mock()
|
|
|
|
# Test stop at position >= 50 with no movement
|
|
mock_instance.devices["802"]["current_state"] = 70
|
|
mock_instance.call_subscribers("802")
|
|
await hass.async_block_till_done()
|
|
|
|
await hass.services.async_call(
|
|
COVER_DOMAIN,
|
|
SERVICE_STOP_COVER,
|
|
{ATTR_ENTITY_ID: cover_entity_id},
|
|
blocking=True,
|
|
)
|
|
|
|
# Position >= 50, should send raise_cover
|
|
mock_instance.raise_cover.assert_called_with("802")
|
|
mock_instance.stop_cover.assert_called_with("802")
|
|
|
|
|
|
async def test_cover_stopped_movement_detection(
|
|
hass: HomeAssistant, mock_bridge_with_cover_mocks: MockBridge
|
|
) -> None:
|
|
"""Test that movement direction is set to STOPPED when position doesn't change."""
|
|
mock_instance = mock_bridge_with_cover_mocks
|
|
cover_entity_id = "cover.basement_bedroom_left_shade"
|
|
|
|
# Set initial position
|
|
mock_instance.devices["802"]["current_state"] = 50
|
|
mock_instance.call_subscribers("802")
|
|
await hass.async_block_till_done()
|
|
|
|
# Send same position again - should detect as stopped
|
|
mock_instance.devices["802"]["current_state"] = 50
|
|
mock_instance.call_subscribers("802")
|
|
await hass.async_block_till_done()
|
|
|
|
# Now stop command should use position heuristic (>= 50)
|
|
await hass.services.async_call(
|
|
COVER_DOMAIN,
|
|
SERVICE_STOP_COVER,
|
|
{ATTR_ENTITY_ID: cover_entity_id},
|
|
blocking=True,
|
|
)
|
|
|
|
# Position >= 50 with STOPPED direction, should send raise_cover
|
|
mock_instance.raise_cover.assert_called_with("802")
|
|
mock_instance.stop_cover.assert_called_with("802")
|
|
|
|
|
|
async def test_cover_startup_with_shade_in_motion(
|
|
hass: HomeAssistant, mock_bridge_with_cover_mocks: MockBridge
|
|
) -> None:
|
|
"""Test stop command when HA starts with shade already in motion."""
|
|
mock_instance = mock_bridge_with_cover_mocks
|
|
cover_entity_id = "cover.basement_bedroom_left_shade"
|
|
|
|
# Shade starts at position 50 (simulating HA startup with shade in motion)
|
|
# First stop without seeing movement should use position heuristic
|
|
await hass.services.async_call(
|
|
COVER_DOMAIN,
|
|
SERVICE_STOP_COVER,
|
|
{ATTR_ENTITY_ID: cover_entity_id},
|
|
blocking=True,
|
|
)
|
|
|
|
# Should have used position heuristic since we haven't seen movement yet
|
|
# Initial position is 100 from MockBridge, so >= 50, should send raise_cover
|
|
mock_instance.raise_cover.assert_called_with("802")
|
|
mock_instance.stop_cover.assert_called_with("802")
|
|
|
|
mock_instance.raise_cover.reset_mock()
|
|
mock_instance.stop_cover.reset_mock()
|
|
|
|
# Now simulate shade moving down (shade was actually in motion)
|
|
mock_instance.devices["802"]["current_state"] = 45
|
|
mock_instance.call_subscribers("802")
|
|
await hass.async_block_till_done()
|
|
|
|
# Now we've detected downward movement
|
|
await hass.services.async_call(
|
|
COVER_DOMAIN,
|
|
SERVICE_STOP_COVER,
|
|
{ATTR_ENTITY_ID: cover_entity_id},
|
|
blocking=True,
|
|
)
|
|
|
|
# Should now correctly send lower_cover since we detected downward movement
|
|
mock_instance.lower_cover.assert_called_with("802")
|
|
mock_instance.stop_cover.assert_called_with("802")
|