Files
core/tests/components/enphase_envoy/test_button.py
T

212 lines
7.0 KiB
Python

"""Test Enphase Envoy button platform."""
from unittest.mock import AsyncMock, patch
from freezegun.api import FrozenDateTimeFactory
from pyenphase.exceptions import EnvoyError
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
from homeassistant.components.enphase_envoy.const import Platform
from homeassistant.components.enphase_envoy.coordinator import SCAN_INTERVAL
from homeassistant.components.select import (
ATTR_OPTION,
DOMAIN as SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
)
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
from . import setup_integration
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
SLEEP = "button.acb_1234_sleep"
WAKE = "button.acb_1234_wake"
ALL_SERIALS = ["121000000001", "122000000002"]
@pytest.mark.parametrize("mock_envoy", ["envoy_acb_batt"], indirect=True)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_button(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
mock_envoy: AsyncMock,
config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test button platform entities against snapshot."""
with patch("homeassistant.components.enphase_envoy.PLATFORMS", [Platform.BUTTON]):
await setup_integration(hass, config_entry)
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
@pytest.mark.parametrize(
"mock_envoy",
[
"envoy",
"envoy_1p_metered",
"envoy_nobatt_metered_3p",
"envoy_tot_cons_metered",
"envoy_eu_batt",
],
indirect=True,
)
async def test_no_button(
hass: HomeAssistant,
mock_envoy: AsyncMock,
config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test button platform entities are not created without ACB batteries."""
with patch("homeassistant.components.enphase_envoy.PLATFORMS", [Platform.BUTTON]):
await setup_integration(hass, config_entry)
assert not er.async_entries_for_config_entry(entity_registry, config_entry.entry_id)
@pytest.mark.parametrize("mock_envoy", ["envoy_acb_batt"], indirect=True)
async def test_button_sleep(
hass: HomeAssistant,
mock_envoy: AsyncMock,
config_entry: MockConfigEntry,
) -> None:
"""Test the sleep button calls set_acb_sleep for all batteries."""
with patch("homeassistant.components.enphase_envoy.PLATFORMS", [Platform.BUTTON]):
await setup_integration(hass, config_entry)
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: SLEEP},
blocking=True,
)
# Default SOC band is derived from the battery configured with sleep thresholds
mock_envoy.set_acb_sleep.assert_awaited_once_with(
[
{"serial_num": serial, "sleep_min_soc": 25, "sleep_max_soc": 30}
for serial in ALL_SERIALS
]
)
@pytest.mark.parametrize("mock_envoy", ["envoy_acb_batt"], indirect=True)
async def test_button_wake(
hass: HomeAssistant,
mock_envoy: AsyncMock,
config_entry: MockConfigEntry,
) -> None:
"""Test the wake button calls clear_acb_sleep for all batteries."""
with patch("homeassistant.components.enphase_envoy.PLATFORMS", [Platform.BUTTON]):
await setup_integration(hass, config_entry)
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: WAKE},
blocking=True,
)
mock_envoy.clear_acb_sleep.assert_awaited_once_with(ALL_SERIALS)
@pytest.mark.parametrize("mock_envoy", ["envoy_acb_batt"], indirect=True)
async def test_button_sleep_uses_selected_soc(
hass: HomeAssistant,
mock_envoy: AsyncMock,
config_entry: MockConfigEntry,
) -> None:
"""Test the sleep button applies the SOC band chosen in the select entity."""
with patch(
"homeassistant.components.enphase_envoy.PLATFORMS",
[Platform.BUTTON, Platform.SELECT],
):
await setup_integration(hass, config_entry)
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: "select.acb_1234_battery_sleep_soc_target",
ATTR_OPTION: "50-55",
},
blocking=True,
)
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: SLEEP},
blocking=True,
)
mock_envoy.set_acb_sleep.assert_awaited_once_with(
[
{"serial_num": serial, "sleep_min_soc": 50, "sleep_max_soc": 55}
for serial in ALL_SERIALS
]
)
@pytest.mark.parametrize("mock_envoy", ["envoy_acb_batt"], indirect=True)
async def test_button_error(
hass: HomeAssistant,
mock_envoy: AsyncMock,
config_entry: MockConfigEntry,
) -> None:
"""Test button press surfaces Envoy errors as HomeAssistantError."""
with patch("homeassistant.components.enphase_envoy.PLATFORMS", [Platform.BUTTON]):
await setup_integration(hass, config_entry)
mock_envoy.set_acb_sleep.side_effect = EnvoyError("Test")
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: SLEEP},
blocking=True,
)
@pytest.mark.parametrize("mock_envoy", ["envoy_acb_batt"], indirect=True)
async def test_button_value_error(
hass: HomeAssistant,
mock_envoy: AsyncMock,
config_entry: MockConfigEntry,
) -> None:
"""Test button press surfaces pyenphase ValueError as HomeAssistantError."""
with patch("homeassistant.components.enphase_envoy.PLATFORMS", [Platform.BUTTON]):
await setup_integration(hass, config_entry)
mock_envoy.set_acb_sleep.side_effect = ValueError("invalid soc")
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: SLEEP},
blocking=True,
)
@pytest.mark.parametrize("mock_envoy", ["envoy_acb_batt"], indirect=True)
async def test_button_no_inventory(
hass: HomeAssistant,
mock_envoy: AsyncMock,
config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test the ACB controls become unavailable when the inventory disappears."""
with patch("homeassistant.components.enphase_envoy.PLATFORMS", [Platform.BUTTON]):
await setup_integration(hass, config_entry)
assert (state := hass.states.get(SLEEP))
assert state.state != STATE_UNAVAILABLE
mock_envoy.data.acb_inventory = None
mock_envoy.data.raw = {"changed": True}
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
assert (state := hass.states.get(SLEEP))
assert state.state == STATE_UNAVAILABLE