1
0
mirror of https://github.com/home-assistant/core.git synced 2026-07-13 09:38:42 +01:00
Files
core/tests/components/broadlink/test_remote.py
T
Andrei Nevedomskii 492a03a814 Fix broadlink not updating state (#169942)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-07-08 22:49:50 +02:00

198 lines
6.7 KiB
Python

"""Tests for Broadlink remotes."""
from base64 import b64decode
from unittest.mock import call
from broadlink.exceptions import BroadlinkException
from freezegun.api import FrozenDateTimeFactory
import pytest
from homeassistant.components.broadlink.const import DOMAIN
from homeassistant.components.broadlink.updater import BroadlinkRMUpdateManager
from homeassistant.components.remote import (
DOMAIN as REMOTE_DOMAIN,
SERVICE_SEND_COMMAND,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
)
from homeassistant.const import (
ATTR_FRIENDLY_NAME,
STATE_OFF,
STATE_ON,
STATE_UNAVAILABLE,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from . import get_device
from tests.common import async_fire_time_changed
REMOTE_DEVICES = ["Entrance", "Living Room", "Office", "Garage"]
IR_PACKET = (
"JgBGAJKVETkRORA6ERQRFBEUERQRFBE5ETkQOhAVEBUQFREUEBUQ"
"OhEUERQRORE5EBURFBA6EBUQOhE5EBUQFRA6EDoRFBEADQUAAA=="
)
async def test_remote_setup_works(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test a successful setup with all remotes."""
for device in map(get_device, REMOTE_DEVICES):
mock_setup = await device.setup_entry(hass)
device_entry = device_registry.async_get_device(
identifiers={(DOMAIN, mock_setup.entry.unique_id)}
)
entries = er.async_entries_for_device(entity_registry, device_entry.id)
remotes = [entry for entry in entries if entry.domain == Platform.REMOTE]
assert len(remotes) == 1
remote = remotes[0]
assert (
hass.states.get(remote.entity_id).attributes[ATTR_FRIENDLY_NAME]
== device.name
)
assert hass.states.get(remote.entity_id).state == STATE_ON
assert mock_setup.api.auth.call_count == 1
async def test_remote_send_command(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test sending a command with all remotes."""
for device in map(get_device, REMOTE_DEVICES):
mock_setup = await device.setup_entry(hass)
device_entry = device_registry.async_get_device(
identifiers={(DOMAIN, mock_setup.entry.unique_id)}
)
entries = er.async_entries_for_device(entity_registry, device_entry.id)
remotes = [entry for entry in entries if entry.domain == Platform.REMOTE]
assert len(remotes) == 1
remote = remotes[0]
await hass.services.async_call(
REMOTE_DOMAIN,
SERVICE_SEND_COMMAND,
{"entity_id": remote.entity_id, "command": "b64:" + IR_PACKET},
blocking=True,
)
assert mock_setup.api.send_data.call_count == 1
assert mock_setup.api.send_data.call_args == call(b64decode(IR_PACKET))
assert mock_setup.api.auth.call_count == 1
@pytest.mark.parametrize(
("error", "ticks_to_unavailable"),
[
# OSError flips availability on the first failure (fast path).
(OSError("connection refused"), 1),
# A generic BroadlinkException keeps the entity available across the
# first three failed cycles and only flips once SCAN_INTERVAL * 3 has
# elapsed since the last successful update.
(BroadlinkException("update failed"), 4),
],
)
async def test_remote_availability(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
error: Exception,
ticks_to_unavailable: int,
) -> None:
"""Test the remote becomes unavailable on disconnect and recovers on reconnect."""
device = get_device("Garage")
mock_setup = await device.setup_entry(hass)
device_entry = device_registry.async_get_device(
identifiers={(DOMAIN, mock_setup.entry.unique_id)}
)
entries = er.async_entries_for_device(entity_registry, device_entry.id)
remote = next(entry for entry in entries if entry.domain == Platform.REMOTE)
assert hass.states.get(remote.entity_id).state == STATE_ON
mock_setup.api.check_sensors.side_effect = error
for _ in range(ticks_to_unavailable - 1):
freezer.tick(BroadlinkRMUpdateManager.SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert hass.states.get(remote.entity_id).state == STATE_ON
freezer.tick(BroadlinkRMUpdateManager.SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert hass.states.get(remote.entity_id).state == STATE_UNAVAILABLE
mock_setup.api.check_sensors.side_effect = None
freezer.tick(BroadlinkRMUpdateManager.SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert hass.states.get(remote.entity_id).state == STATE_ON
async def test_remote_turn_off_turn_on(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test we do not send commands if the remotes are off."""
for device in map(get_device, REMOTE_DEVICES):
mock_setup = await device.setup_entry(hass)
device_entry = device_registry.async_get_device(
identifiers={(DOMAIN, mock_setup.entry.unique_id)}
)
entries = er.async_entries_for_device(entity_registry, device_entry.id)
remotes = [entry for entry in entries if entry.domain == Platform.REMOTE]
assert len(remotes) == 1
remote = remotes[0]
await hass.services.async_call(
REMOTE_DOMAIN,
SERVICE_TURN_OFF,
{"entity_id": remote.entity_id},
blocking=True,
)
assert hass.states.get(remote.entity_id).state == STATE_OFF
await hass.services.async_call(
REMOTE_DOMAIN,
SERVICE_SEND_COMMAND,
{"entity_id": remote.entity_id, "command": "b64:" + IR_PACKET},
blocking=True,
)
assert mock_setup.api.send_data.call_count == 0
await hass.services.async_call(
REMOTE_DOMAIN,
SERVICE_TURN_ON,
{"entity_id": remote.entity_id},
blocking=True,
)
assert hass.states.get(remote.entity_id).state == STATE_ON
await hass.services.async_call(
REMOTE_DOMAIN,
SERVICE_SEND_COMMAND,
{"entity_id": remote.entity_id, "command": "b64:" + IR_PACKET},
blocking=True,
)
assert mock_setup.api.send_data.call_count == 1
assert mock_setup.api.send_data.call_args == call(b64decode(IR_PACKET))
assert mock_setup.api.auth.call_count == 1