core/tests/components/screenlogic/test_services.py

497 lines
14 KiB
Python

"""Tests for ScreenLogic integration service calls."""
from typing import Any
from unittest.mock import DEFAULT, AsyncMock, patch
import pytest
from screenlogicpy import ScreenLogicGateway
from screenlogicpy.device_const.system import COLOR_MODE
from typing_extensions import AsyncGenerator
from homeassistant.components.screenlogic import DOMAIN
from homeassistant.components.screenlogic.const import (
ATTR_COLOR_MODE,
ATTR_CONFIG_ENTRY,
ATTR_RUNTIME,
SERVICE_SET_COLOR_MODE,
SERVICE_START_SUPER_CHLORINATION,
SERVICE_STOP_SUPER_CHLORINATION,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_AREA_ID, ATTR_DEVICE_ID, ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import device_registry as dr
from homeassistant.util import slugify
from . import (
DATA_FULL_CHEM,
DATA_FULL_CHEM_CHLOR,
DATA_MIN_ENTITY_CLEANUP,
GATEWAY_DISCOVERY_IMPORT_PATH,
MOCK_ADAPTER_MAC,
MOCK_ADAPTER_NAME,
MOCK_CONFIG_ENTRY_ID,
MOCK_DEVICE_AREA,
stub_async_connect,
)
from tests.common import MockConfigEntry
NON_SL_CONFIG_ENTRY_ID = "test"
@pytest.fixture(name="dataset")
def dataset_fixture():
"""Define the default dataset for service tests."""
return DATA_FULL_CHEM
@pytest.fixture(name="service_fixture")
async def setup_screenlogic_services_fixture(
hass: HomeAssistant,
request: pytest.FixtureRequest,
device_registry: dr.DeviceRegistry,
mock_config_entry: MockConfigEntry,
) -> AsyncGenerator[dict[str, Any]]:
"""Define the setup for a patched screenlogic integration."""
data = (
marker.args[0]
if (marker := request.node.get_closest_marker("dataset")) is not None
else DATA_FULL_CHEM
)
def _service_connect(*args, **kwargs):
return stub_async_connect(data, *args, **kwargs)
mock_config_entry.add_to_hass(hass)
device: dr.DeviceEntry = device_registry.async_get_or_create(
config_entry_id=mock_config_entry.entry_id,
connections={(dr.CONNECTION_NETWORK_MAC, MOCK_ADAPTER_MAC)},
suggested_area=MOCK_DEVICE_AREA,
)
with (
patch(
GATEWAY_DISCOVERY_IMPORT_PATH,
return_value={},
),
patch.multiple(
ScreenLogicGateway,
async_connect=_service_connect,
is_connected=True,
_async_connected_request=DEFAULT,
async_set_color_lights=DEFAULT,
async_set_scg_config=DEFAULT,
) as gateway,
):
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
yield {"gateway": gateway, "device": device}
@pytest.mark.parametrize(
("data", "target"),
[
(
{
ATTR_COLOR_MODE: COLOR_MODE.ALL_OFF.name.lower(),
ATTR_CONFIG_ENTRY: MOCK_CONFIG_ENTRY_ID,
},
None,
),
(
{
ATTR_COLOR_MODE: COLOR_MODE.ALL_ON.name.lower(),
},
{
ATTR_AREA_ID: MOCK_DEVICE_AREA,
},
),
(
{
ATTR_COLOR_MODE: COLOR_MODE.ALL_ON.name.lower(),
},
{
ATTR_ENTITY_ID: f"{Platform.SENSOR}.{slugify(f'{MOCK_ADAPTER_NAME} Air Temperature')}",
},
),
],
)
async def test_service_set_color_mode(
hass: HomeAssistant,
service_fixture: dict[str, Any],
data: dict[str, Any],
target: dict[str, Any],
) -> None:
"""Test set_color_mode service."""
mocked_async_set_color_lights: AsyncMock = service_fixture["gateway"][
"async_set_color_lights"
]
assert hass.services.has_service(DOMAIN, SERVICE_SET_COLOR_MODE)
non_screenlogic_entry = MockConfigEntry(entry_id="test")
non_screenlogic_entry.add_to_hass(hass)
await hass.services.async_call(
DOMAIN,
SERVICE_SET_COLOR_MODE,
service_data=data,
blocking=True,
target=target,
)
mocked_async_set_color_lights.assert_awaited_once()
async def test_service_set_color_mode_with_device(
hass: HomeAssistant,
service_fixture: dict[str, Any],
) -> None:
"""Test set_color_mode service with a device target."""
mocked_async_set_color_lights: AsyncMock = service_fixture["gateway"][
"async_set_color_lights"
]
assert hass.services.has_service(DOMAIN, SERVICE_SET_COLOR_MODE)
sl_device: dr.DeviceEntry = service_fixture["device"]
await hass.services.async_call(
DOMAIN,
SERVICE_SET_COLOR_MODE,
service_data={ATTR_COLOR_MODE: COLOR_MODE.ALL_ON.name.lower()},
blocking=True,
target={ATTR_DEVICE_ID: sl_device.id},
)
mocked_async_set_color_lights.assert_awaited_once()
@pytest.mark.parametrize(
("data", "target", "error_msg"),
[
(
{
ATTR_COLOR_MODE: COLOR_MODE.ALL_OFF.name.lower(),
ATTR_CONFIG_ENTRY: "invalidconfigentry",
},
None,
f"Failed to call service '{SERVICE_SET_COLOR_MODE}'. Config entry "
"'invalidconfigentry' not found",
),
(
{
ATTR_COLOR_MODE: COLOR_MODE.ALL_OFF.name.lower(),
ATTR_CONFIG_ENTRY: NON_SL_CONFIG_ENTRY_ID,
},
None,
f"Failed to call service '{SERVICE_SET_COLOR_MODE}'. Config entry "
"'test' is not a screenlogic config",
),
(
{
ATTR_COLOR_MODE: COLOR_MODE.ALL_ON.name.lower(),
},
{
ATTR_AREA_ID: "invalidareaid",
},
f"Failed to call service '{SERVICE_SET_COLOR_MODE}'. Config entry for "
"target not found",
),
(
{
ATTR_COLOR_MODE: COLOR_MODE.ALL_ON.name.lower(),
},
{
ATTR_DEVICE_ID: "invaliddeviceid",
},
f"Failed to call service '{SERVICE_SET_COLOR_MODE}'. Config entry for "
"target not found",
),
(
{
ATTR_COLOR_MODE: COLOR_MODE.ALL_ON.name.lower(),
},
{
ATTR_ENTITY_ID: "sensor.invalidentityid",
},
f"Failed to call service '{SERVICE_SET_COLOR_MODE}'. Config entry for "
"target not found",
),
],
)
async def test_service_set_color_mode_error(
hass: HomeAssistant,
service_fixture: dict[str, Any],
data: dict[str, Any],
target: dict[str, Any],
error_msg: str,
) -> None:
"""Test set_color_mode service error cases."""
mocked_async_set_color_lights: AsyncMock = service_fixture["gateway"][
"async_set_color_lights"
]
non_screenlogic_entry = MockConfigEntry(entry_id=NON_SL_CONFIG_ENTRY_ID)
non_screenlogic_entry.add_to_hass(hass)
assert hass.services.has_service(DOMAIN, SERVICE_SET_COLOR_MODE)
with pytest.raises(
ServiceValidationError,
match=error_msg,
):
await hass.services.async_call(
DOMAIN,
SERVICE_SET_COLOR_MODE,
service_data=data,
blocking=True,
target=target,
)
mocked_async_set_color_lights.assert_not_awaited()
@pytest.mark.dataset(DATA_FULL_CHEM_CHLOR)
@pytest.mark.parametrize(
("data", "target"),
[
(
{
ATTR_CONFIG_ENTRY: MOCK_CONFIG_ENTRY_ID,
ATTR_RUNTIME: 24,
},
None,
),
],
)
async def test_service_start_super_chlorination(
hass: HomeAssistant,
service_fixture: dict[str, Any],
data: dict[str, Any],
target: dict[str, Any],
) -> None:
"""Test start_super_chlorination service."""
mocked_async_set_scg_config: AsyncMock = service_fixture["gateway"][
"async_set_scg_config"
]
assert hass.services.has_service(DOMAIN, SERVICE_START_SUPER_CHLORINATION)
await hass.services.async_call(
DOMAIN,
SERVICE_START_SUPER_CHLORINATION,
service_data=data,
blocking=True,
target=target,
)
mocked_async_set_scg_config.assert_awaited_once()
@pytest.mark.parametrize(
("data", "target", "error_msg"),
[
(
{
ATTR_CONFIG_ENTRY: "invalidconfigentry",
ATTR_RUNTIME: 24,
},
None,
f"Failed to call service '{SERVICE_START_SUPER_CHLORINATION}'. "
"Config entry 'invalidconfigentry' not found",
),
(
{
ATTR_CONFIG_ENTRY: MOCK_CONFIG_ENTRY_ID,
ATTR_RUNTIME: 24,
},
None,
f"Equipment configuration for {MOCK_ADAPTER_NAME} does not"
f" support {SERVICE_START_SUPER_CHLORINATION}",
),
],
)
async def test_service_start_super_chlorination_error(
hass: HomeAssistant,
service_fixture: dict[str, Any],
data: dict[str, Any],
target: dict[str, Any],
error_msg: str,
) -> None:
"""Test start_super_chlorination service error cases."""
mocked_async_set_scg_config: AsyncMock = service_fixture["gateway"][
"async_set_scg_config"
]
assert hass.services.has_service(DOMAIN, SERVICE_START_SUPER_CHLORINATION)
with pytest.raises(
ServiceValidationError,
match=error_msg,
):
await hass.services.async_call(
DOMAIN,
SERVICE_START_SUPER_CHLORINATION,
service_data=data,
blocking=True,
target=target,
)
mocked_async_set_scg_config.assert_not_awaited()
@pytest.mark.dataset(DATA_FULL_CHEM_CHLOR)
@pytest.mark.parametrize(
("data", "target"),
[
(
{
ATTR_CONFIG_ENTRY: MOCK_CONFIG_ENTRY_ID,
},
None,
),
],
)
async def test_service_stop_super_chlorination(
hass: HomeAssistant,
service_fixture: dict[str, Any],
data: dict[str, Any],
target: dict[str, Any],
) -> None:
"""Test stop_super_chlorination service."""
mocked_async_set_scg_config: AsyncMock = service_fixture["gateway"][
"async_set_scg_config"
]
assert hass.services.has_service(DOMAIN, SERVICE_STOP_SUPER_CHLORINATION)
await hass.services.async_call(
DOMAIN,
SERVICE_STOP_SUPER_CHLORINATION,
service_data=data,
blocking=True,
target=target,
)
mocked_async_set_scg_config.assert_awaited_once()
@pytest.mark.parametrize(
("data", "target", "error_msg"),
[
(
{
ATTR_CONFIG_ENTRY: "invalidconfigentry",
},
None,
f"Failed to call service '{SERVICE_STOP_SUPER_CHLORINATION}'. "
"Config entry 'invalidconfigentry' not found",
),
(
{
ATTR_CONFIG_ENTRY: MOCK_CONFIG_ENTRY_ID,
},
None,
f"Equipment configuration for {MOCK_ADAPTER_NAME} does not"
f" support {SERVICE_STOP_SUPER_CHLORINATION}",
),
],
)
async def test_service_stop_super_chlorination_error(
hass: HomeAssistant,
service_fixture: dict[str, Any],
data: dict[str, Any],
target: dict[str, Any],
error_msg: str,
) -> None:
"""Test stop_super_chlorination service error cases."""
mocked_async_set_scg_config: AsyncMock = service_fixture["gateway"][
"async_set_scg_config"
]
assert hass.services.has_service(DOMAIN, SERVICE_STOP_SUPER_CHLORINATION)
with pytest.raises(
ServiceValidationError,
match=error_msg,
):
await hass.services.async_call(
DOMAIN,
SERVICE_STOP_SUPER_CHLORINATION,
service_data=data,
blocking=True,
target=target,
)
mocked_async_set_scg_config.assert_not_awaited()
async def test_service_config_entry_not_loaded(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the error case of config not loaded."""
mock_config_entry.add_to_hass(hass)
_ = device_registry.async_get_or_create(
config_entry_id=mock_config_entry.entry_id,
connections={(dr.CONNECTION_NETWORK_MAC, MOCK_ADAPTER_MAC)},
)
mock_set_color_lights = AsyncMock()
with (
patch(
GATEWAY_DISCOVERY_IMPORT_PATH,
return_value={},
),
patch.multiple(
ScreenLogicGateway,
async_connect=lambda *args, **kwargs: stub_async_connect(
DATA_MIN_ENTITY_CLEANUP, *args, **kwargs
),
async_disconnect=DEFAULT,
is_connected=True,
_async_connected_request=DEFAULT,
async_set_color_lights=mock_set_color_lights,
),
):
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert hass.services.has_service(DOMAIN, SERVICE_SET_COLOR_MODE)
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
await hass.config_entries.async_unload(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
with pytest.raises(
ServiceValidationError,
match=f"Failed to call service '{SERVICE_SET_COLOR_MODE}'. "
f"Config entry '{MOCK_CONFIG_ENTRY_ID}' not loaded",
):
await hass.services.async_call(
DOMAIN,
SERVICE_SET_COLOR_MODE,
service_data={
ATTR_COLOR_MODE: COLOR_MODE.ALL_OFF.name.lower(),
ATTR_CONFIG_ENTRY: MOCK_CONFIG_ENTRY_ID,
},
blocking=True,
)
mock_set_color_lights.assert_not_awaited()