Refactor tradfri fan tests (#87999)

pull/88012/head
Martin Hjelmare 2023-02-13 15:40:21 +01:00 committed by GitHub
parent ae06d9fd5c
commit 8993a36e27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 289 additions and 133 deletions

View File

@ -2,7 +2,7 @@
from __future__ import annotations
from collections.abc import Generator
from unittest.mock import MagicMock, Mock, PropertyMock, patch
from unittest.mock import MagicMock, Mock, patch
import pytest
@ -79,19 +79,3 @@ def mock_api_factory(mock_api) -> Generator[MagicMock, None, None]:
factory.init.return_value = factory.return_value
factory.return_value.request = mock_api
yield factory.return_value
@pytest.fixture(autouse=True)
def setup(request):
"""Set up patches for pytradfri methods for the fan platform.
This is used in test_fan as well as in test_sensor.
"""
with patch(
"pytradfri.device.AirPurifierControl.raw",
new_callable=PropertyMock,
return_value=[{"mock": "mock"}],
), patch(
"pytradfri.device.AirPurifierControl.air_purifiers",
):
yield

View File

@ -0,0 +1,32 @@
{
"3": {
"0": "IKEA of Sweden",
"1": "STARKVIND Air purifier",
"2": "",
"3": "1.0.033",
"6": 1,
"7": 4364
},
"5750": 10,
"9001": "Test",
"9002": 1633096623,
"9003": 65551,
"9019": 1,
"9020": 1633096633,
"9054": 0,
"15025": [
{
"5900": 0,
"5902": 2,
"5903": 0,
"5904": 259200,
"5905": 0,
"5906": 0,
"5907": 5,
"5908": 10,
"5909": 2,
"5910": 259198,
"9003": 0
}
]
}

View File

@ -1,15 +1,33 @@
"""Tests for Tradfri diagnostics."""
from unittest.mock import MagicMock, Mock
from unittest.mock import MagicMock, Mock, PropertyMock, patch
import pytest
from homeassistant.core import HomeAssistant
from .common import setup_integration
from .test_fan import mock_fan
from .test_sensor import mock_fan
from tests.components.diagnostics import get_diagnostics_for_config_entry
from tests.typing import ClientSessionGenerator
@pytest.fixture(autouse=True)
def setup(request):
"""Set up patches for pytradfri methods for the fan platform.
This is used in test_fan as well as in test_sensor.
"""
with patch(
"pytradfri.device.AirPurifierControl.raw",
new_callable=PropertyMock,
return_value=[{"mock": "mock"}],
), patch(
"pytradfri.device.AirPurifierControl.air_purifiers",
):
yield
async def test_diagnostics(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
@ -34,4 +52,4 @@ async def test_diagnostics(
assert isinstance(result, dict)
assert result["gateway_version"] == "1.2.1234"
assert len(result["device_data"]) == 1
assert result["device_data"] == ["model"]

View File

@ -1,144 +1,216 @@
"""Tradfri fan (recognised as air purifiers in the IKEA ecosystem) platform tests."""
from __future__ import annotations
import json
from typing import Any
from unittest.mock import MagicMock, Mock
import pytest
from pytradfri.const import (
ATTR_AIR_PURIFIER_FAN_SPEED,
ATTR_AIR_PURIFIER_MODE,
ATTR_REACHABLE_STATE,
ROOT_AIR_PURIFIER,
)
from pytradfri.device import Device
from pytradfri.device.air_purifier import AirPurifier
from pytradfri.device.air_purifier_control import AirPurifierControl
from .common import setup_integration
from homeassistant.components.fan import (
ATTR_PERCENTAGE,
ATTR_PERCENTAGE_STEP,
ATTR_PRESET_MODE,
ATTR_PRESET_MODES,
DOMAIN as FAN_DOMAIN,
SERVICE_SET_PERCENTAGE,
SERVICE_SET_PRESET_MODE,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
)
from homeassistant.components.tradfri.const import DOMAIN
from homeassistant.const import (
ATTR_SUPPORTED_FEATURES,
STATE_OFF,
STATE_ON,
STATE_UNAVAILABLE,
)
from homeassistant.core import HomeAssistant
from .common import setup_integration, trigger_observe_callback
from tests.common import load_fixture
def mock_fan(test_features=None, test_state=None, device_number=0):
"""Mock a tradfri fan/air purifier."""
if test_features is None:
test_features = {}
if test_state is None:
test_state = {}
mock_fan_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_fan = Mock(
id=f"mock-fan-id-{device_number}",
reachable=True,
observe=Mock(),
device_info=dev_info_mock,
has_light_control=False,
has_socket_control=False,
has_blind_control=False,
has_signal_repeater_control=False,
has_air_purifier_control=True,
)
_mock_fan.name = f"tradfri_fan_{device_number}"
air_purifier_control = AirPurifierControl(_mock_fan)
# Store the initial state.
setattr(air_purifier_control, "air_purifiers", [mock_fan_data])
_mock_fan.air_purifier_control = air_purifier_control
return _mock_fan
@pytest.fixture(scope="module")
def air_purifier_response() -> dict[str, Any]:
"""Return an air purifier response."""
return json.loads(load_fixture("air_purifier.json", DOMAIN))
async def test_fan(hass, mock_gateway, mock_api_factory):
"""Test that fans are correctly added."""
state = {"fan_speed": 10, "air_quality": 12}
mock_gateway.mock_devices.append(mock_fan(test_state=state))
await setup_integration(hass)
fan_1 = hass.states.get("fan.tradfri_fan_0")
assert fan_1 is not None
assert fan_1.state == "on"
assert fan_1.attributes["percentage"] == 18
assert fan_1.attributes["preset_modes"] == ["Auto"]
assert fan_1.attributes["supported_features"] == 9
@pytest.fixture
def air_purifier(air_purifier_response: dict[str, Any]) -> AirPurifier:
"""Return air purifier."""
device = Device(air_purifier_response)
air_purifier_control = device.air_purifier_control
assert air_purifier_control
return air_purifier_control.air_purifiers[0]
async def test_fan_observed(hass, mock_gateway, mock_api_factory):
"""Test that fans are correctly observed."""
state = {"fan_speed": 10, "air_quality": 12}
fan = mock_fan(test_state=state)
mock_gateway.mock_devices.append(fan)
await setup_integration(hass)
assert len(fan.observe.mock_calls) > 0
async def test_fan_available(hass, mock_gateway, mock_api_factory):
async def test_fan_available(
hass: HomeAssistant,
mock_gateway: Mock,
mock_api_factory: MagicMock,
air_purifier: AirPurifier,
) -> None:
"""Test fan available property."""
fan = mock_fan(test_state={"fan_speed": 10, "air_quality": 12}, device_number=1)
fan.reachable = True
fan2 = mock_fan(test_state={"fan_speed": 10, "air_quality": 12}, device_number=2)
fan2.reachable = False
mock_gateway.mock_devices.append(fan)
mock_gateway.mock_devices.append(fan2)
entity_id = "fan.test"
device = air_purifier.device
mock_gateway.mock_devices.append(device)
await setup_integration(hass)
assert hass.states.get("fan.tradfri_fan_1").state == "on"
assert hass.states.get("fan.tradfri_fan_2").state == "unavailable"
state = hass.states.get(entity_id)
assert state
assert state.state == STATE_OFF
assert state.attributes[ATTR_PERCENTAGE] == 18
assert state.attributes[ATTR_PERCENTAGE_STEP] == pytest.approx(2.040816)
assert state.attributes[ATTR_PRESET_MODES] == ["Auto"]
assert state.attributes[ATTR_PRESET_MODE] is None
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 9
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",
(
"service, service_data, device_state, expected_state, "
"expected_percentage, expected_preset_mode"
),
[
(
{"percentage": 50},
"on",
SERVICE_SET_PERCENTAGE,
{ATTR_PERCENTAGE: 50},
{
ATTR_AIR_PURIFIER_FAN_SPEED: 25,
ATTR_AIR_PURIFIER_MODE: 25,
},
STATE_ON,
49,
None,
),
(
SERVICE_SET_PERCENTAGE,
{ATTR_PERCENTAGE: 0},
{
ATTR_AIR_PURIFIER_FAN_SPEED: 0,
ATTR_AIR_PURIFIER_MODE: 0,
},
STATE_OFF,
None,
None,
),
(
SERVICE_TURN_ON,
{ATTR_PERCENTAGE: 50},
{
ATTR_AIR_PURIFIER_FAN_SPEED: 25,
ATTR_AIR_PURIFIER_MODE: 25,
},
STATE_ON,
49,
None,
),
(
SERVICE_TURN_ON,
{ATTR_PRESET_MODE: "Auto"},
{
ATTR_AIR_PURIFIER_MODE: 1,
},
STATE_ON,
18,
"Auto",
),
(
SERVICE_TURN_ON,
{},
{
ATTR_AIR_PURIFIER_MODE: 1,
},
STATE_ON,
18,
"Auto",
),
(
SERVICE_SET_PRESET_MODE,
{ATTR_PRESET_MODE: "Auto"},
{
ATTR_AIR_PURIFIER_MODE: 1,
},
STATE_ON,
18,
"Auto",
),
(
SERVICE_TURN_OFF,
{},
{
ATTR_AIR_PURIFIER_FAN_SPEED: 0,
ATTR_AIR_PURIFIER_MODE: 0,
},
STATE_OFF,
None,
None,
),
({"percentage": 0}, "off"),
],
)
async def test_set_percentage(
hass,
mock_gateway,
mock_api_factory,
test_data,
expected_result,
):
"""Test setting speed of a fan."""
# Note pytradfri style, not hass. Values not really important.
initial_state = {"percentage": 10, "fan_speed": 3, "air_quality": 12}
# Setup the gateway with a mock fan.
fan = mock_fan(test_state=initial_state, device_number=0)
mock_gateway.mock_devices.append(fan)
async def test_services(
hass: HomeAssistant,
mock_gateway: Mock,
mock_api_factory: MagicMock,
air_purifier: AirPurifier,
service: str,
service_data: dict[str, Any],
device_state: dict[str, Any],
expected_state: str,
expected_percentage: int | None,
expected_preset_mode: str | None,
) -> None:
"""Test fan services."""
entity_id = "fan.test"
device = air_purifier.device
mock_gateway.mock_devices.append(device)
await setup_integration(hass)
# Use the turn_on service call to change the fan state.
state = hass.states.get(entity_id)
assert state
assert state.state == STATE_OFF
assert state.attributes[ATTR_PERCENTAGE] == 18
assert state.attributes[ATTR_PERCENTAGE_STEP] == pytest.approx(2.040816)
assert state.attributes[ATTR_PRESET_MODES] == ["Auto"]
assert state.attributes[ATTR_PRESET_MODE] is None
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 9
await hass.services.async_call(
"fan",
"set_percentage",
{"entity_id": "fan.tradfri_fan_0", **test_data},
FAN_DOMAIN,
service,
{"entity_id": entity_id, **service_data},
blocking=True,
)
await hass.async_block_till_done()
# Check that the fan is observed.
mock_func = fan.observe
assert len(mock_func.mock_calls) > 0
_, callkwargs = mock_func.call_args
assert "callback" in callkwargs
# Callback function to refresh fan state.
callback = callkwargs["callback"]
await trigger_observe_callback(
hass,
mock_gateway,
device,
{ROOT_AIR_PURIFIER: [device_state]},
)
responses = mock_gateway.mock_responses
mock_gateway_response = responses[0]
# A KeyError is raised if we don't this to the response code
mock_gateway_response["15025"][0].update({"5908": 10, "5907": 12, "5910": 20})
# Use the callback function to update the fan state.
dev = Device(mock_gateway_response)
fan_data = AirPurifier(dev, 0)
fan.air_purifier_control.air_purifiers[0] = fan_data
callback(fan)
await hass.async_block_till_done()
# Check that the state is correct.
state = hass.states.get("fan.tradfri_fan_0")
assert state.state == expected_result
state = hass.states.get(entity_id)
assert state
assert state.state == expected_state
assert state.attributes[ATTR_PERCENTAGE] == expected_percentage
assert state.attributes[ATTR_PRESET_MODE] == expected_preset_mode

View File

@ -1,18 +1,68 @@
"""Tradfri sensor platform tests."""
from __future__ import annotations
from unittest.mock import MagicMock, Mock
from unittest.mock import MagicMock, Mock, PropertyMock, patch
import pytest
from pytradfri.device.air_purifier_control import AirPurifierControl
from homeassistant.components import tradfri
from homeassistant.helpers import entity_registry as er
from . import GATEWAY_ID
from .common import setup_integration
from .test_fan import mock_fan
from tests.common import MockConfigEntry
@pytest.fixture(autouse=True)
def setup(request):
"""Set up patches for pytradfri methods for the fan platform.
This is used in test_fan as well as in test_sensor.
"""
with patch(
"pytradfri.device.AirPurifierControl.raw",
new_callable=PropertyMock,
return_value=[{"mock": "mock"}],
), patch(
"pytradfri.device.AirPurifierControl.air_purifiers",
):
yield
def mock_fan(test_features=None, test_state=None, device_number=0):
"""Mock a tradfri fan/air purifier."""
if test_features is None:
test_features = {}
if test_state is None:
test_state = {}
mock_fan_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_fan = Mock(
id=f"mock-fan-id-{device_number}",
reachable=True,
observe=Mock(),
device_info=dev_info_mock,
has_light_control=False,
has_socket_control=False,
has_blind_control=False,
has_signal_repeater_control=False,
has_air_purifier_control=True,
)
_mock_fan.name = f"tradfri_fan_{device_number}"
air_purifier_control = AirPurifierControl(_mock_fan)
# Store the initial state.
setattr(air_purifier_control, "air_purifiers", [mock_fan_data])
_mock_fan.air_purifier_control = air_purifier_control
return _mock_fan
def mock_sensor(test_state: list, device_number=0):
"""Mock a tradfri sensor."""
dev_info_mock = MagicMock()