diff --git a/tests/components/tradfri/fixtures/blind.json b/tests/components/tradfri/fixtures/blind.json new file mode 100644 index 00000000000..23655c46370 --- /dev/null +++ b/tests/components/tradfri/fixtures/blind.json @@ -0,0 +1,19 @@ +{ + "3": { + "0": "IKEA of Sweden", + "1": "FYRTUR block-out roller blind", + "2": "", + "3": "2.2.007", + "6": 3, + "9": 77 + }, + "5750": 7, + "9001": "Test", + "9002": 1566141494, + "9003": 65601, + "9019": 1, + "9020": 1566402653, + "9054": 0, + "9084": " 9d 58 b0 2 4 6a df be 77 e5 c1 e0 a2 26 2e 57", + "15015": [{ "5536": 40.0, "9003": 0 }] +} diff --git a/tests/components/tradfri/test_cover.py b/tests/components/tradfri/test_cover.py index 663e7209619..d4b110948ab 100644 --- a/tests/components/tradfri/test_cover.py +++ b/tests/components/tradfri/test_cover.py @@ -1,154 +1,109 @@ """Tradfri cover (recognised as blinds in the IKEA ecosystem) platform tests.""" +from __future__ import annotations -from unittest.mock import MagicMock, Mock, PropertyMock, patch +import json +from typing import Any +from unittest.mock import MagicMock, Mock import pytest +from pytradfri.const import ATTR_REACHABLE_STATE from pytradfri.device import Device from pytradfri.device.blind import Blind -from pytradfri.device.blind_control import BlindControl -from .common import setup_integration +from homeassistant.components.cover import ATTR_CURRENT_POSITION, DOMAIN as COVER_DOMAIN +from homeassistant.components.tradfri.const import DOMAIN +from homeassistant.const import STATE_CLOSED, STATE_OPEN, STATE_UNAVAILABLE +from homeassistant.core import HomeAssistant + +from .common import setup_integration, trigger_observe_callback + +from tests.common import load_fixture -@pytest.fixture(autouse=True, scope="module") -def setup(request): - """Set up patches for pytradfri methods.""" - with patch( - "pytradfri.device.BlindControl.raw", - new_callable=PropertyMock, - return_value=[{"mock": "mock"}], - ), patch( - "pytradfri.device.BlindControl.blinds", - ): - yield +@pytest.fixture(scope="module") +def blind_response() -> dict[str, Any]: + """Return a blind response.""" + return json.loads(load_fixture("blind.json", DOMAIN)) -def mock_cover(test_features=None, test_state=None, device_number=0): - """Mock a tradfri cover/blind.""" - if test_features is None: - test_features = {} - if test_state is None: - test_state = {} - mock_cover_data = Mock(**test_state) - - dev_info_mock = MagicMock() - dev_info_mock.manufacturer = "manufacturer" - dev_info_mock.model_number = "model" - dev_info_mock.firmware_version = "1.2.3" - _mock_cover = Mock( - id=f"mock-cover-id-{device_number}", - reachable=True, - observe=Mock(), - device_info=dev_info_mock, - has_light_control=False, - has_socket_control=False, - has_blind_control=True, - has_signal_repeater_control=False, - has_air_purifier_control=False, - ) - _mock_cover.name = f"tradfri_cover_{device_number}" - - # Set supported features for the covers. - blind_control = BlindControl(_mock_cover) - - # Store the initial state. - setattr(blind_control, "blinds", [mock_cover_data]) - _mock_cover.blind_control = blind_control - return _mock_cover +@pytest.fixture +def blind(blind_response: dict[str, Any]) -> Blind: + """Return blind.""" + device = Device(blind_response) + blind_control = device.blind_control + assert blind_control + return blind_control.blinds[0] -async def test_cover(hass, mock_gateway, mock_api_factory): - """Test that covers are correctly added.""" - state = { - "current_cover_position": 40, - } - - mock_gateway.mock_devices.append(mock_cover(test_state=state)) - await setup_integration(hass) - - cover_1 = hass.states.get("cover.tradfri_cover_0") - assert cover_1 is not None - assert cover_1.state == "open" - assert cover_1.attributes["current_position"] == 60 - - -async def test_cover_observed(hass, mock_gateway, mock_api_factory): - """Test that covers are correctly observed.""" - state = { - "current_cover_position": 1, - } - - cover = mock_cover(test_state=state) - mock_gateway.mock_devices.append(cover) - await setup_integration(hass) - assert len(cover.observe.mock_calls) > 0 - - -async def test_cover_available(hass, mock_gateway, mock_api_factory): +async def test_cover_available( + hass: HomeAssistant, + mock_gateway: Mock, + mock_api_factory: MagicMock, + blind: Blind, +) -> None: """Test cover available property.""" - - cover = mock_cover(test_state={"current_cover_position": 1}, device_number=1) - cover.reachable = True - - cover2 = mock_cover(test_state={"current_cover_position": 1}, device_number=2) - cover2.reachable = False - - mock_gateway.mock_devices.append(cover) - mock_gateway.mock_devices.append(cover2) + entity_id = "cover.test" + device = blind.device + mock_gateway.mock_devices.append(device) await setup_integration(hass) - assert hass.states.get("cover.tradfri_cover_1").state == "open" - assert hass.states.get("cover.tradfri_cover_2").state == "unavailable" + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_OPEN + assert state.attributes[ATTR_CURRENT_POSITION] == 60 + assert state.attributes["model"] == "FYRTUR block-out roller blind" + + await trigger_observe_callback( + hass, mock_gateway, device, {ATTR_REACHABLE_STATE: 0} + ) + + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_UNAVAILABLE @pytest.mark.parametrize( - "test_data, expected_result", - [({"position": 100}, "open"), ({"position": 0}, "closed")], + "service, service_data, expected_state, expected_position", + [ + ("set_cover_position", {"position": 100}, STATE_OPEN, 100), + ("set_cover_position", {"position": 0}, STATE_CLOSED, 0), + ("open_cover", {}, STATE_OPEN, 100), + ("close_cover", {}, STATE_CLOSED, 0), + ("stop_cover", {}, STATE_OPEN, 60), + ], ) -async def test_set_cover_position( - hass, - mock_gateway, - mock_api_factory, - test_data, - expected_result, -): - """Test setting position of a cover.""" - # Note pytradfri style, not hass. Values not really important. - initial_state = { - "current_cover_position": 0, - } - - # Setup the gateway with a mock cover. - cover = mock_cover(test_state=initial_state, device_number=0) - mock_gateway.mock_devices.append(cover) +async def test_cover_services( + hass: HomeAssistant, + mock_gateway: Mock, + mock_api_factory: MagicMock, + blind: Blind, + service: str, + service_data: dict[str, Any], + expected_state: str, + expected_position: int, +) -> None: + """Test cover services.""" + entity_id = "cover.test" + device = blind.device + mock_gateway.mock_devices.append(device) await setup_integration(hass) - # Use the turn_on service call to change the cover state. + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_OPEN + assert state.attributes[ATTR_CURRENT_POSITION] == 60 + await hass.services.async_call( - "cover", - "set_cover_position", - {"entity_id": "cover.tradfri_cover_0", **test_data}, + COVER_DOMAIN, + service, + {"entity_id": entity_id, **service_data}, blocking=True, ) await hass.async_block_till_done() - # Check that the cover is observed. - mock_func = cover.observe - assert len(mock_func.mock_calls) > 0 - _, callkwargs = mock_func.call_args - assert "callback" in callkwargs - # Callback function to refresh cover state. - callback = callkwargs["callback"] + await trigger_observe_callback(hass, mock_gateway, device) - responses = mock_gateway.mock_responses - - # Use the callback function to update the cover state. - dev = Device(responses[0]) - cover_data = Blind(dev, 0) - cover.blind_control.blinds[0] = cover_data - callback(cover) - await hass.async_block_till_done() - - # Check that the state is correct. - state = hass.states.get("cover.tradfri_cover_0") - assert state.state == expected_result + state = hass.states.get(entity_id) + assert state + assert state.state == expected_state + assert state.attributes[ATTR_CURRENT_POSITION] == expected_position