Swallow error 40000 for songpal power on/off (#80563)

* Swallow error 40000 for songpal power on/off

* Move ERROR_REQUEST_RETRY to consts

* Add tests for the swallow exception behavior

* Update tests/components/songpal/test_media_player.py

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
Teemu R 2023-05-31 04:58:41 +02:00 committed by GitHub
parent 81561d4d3e
commit 23c5e60be0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 21 deletions

View File

@ -3,3 +3,5 @@ DOMAIN = "songpal"
SET_SOUND_SETTING = "set_sound_setting"
CONF_ENDPOINT = "endpoint"
ERROR_REQUEST_RETRY = 40000

View File

@ -34,7 +34,7 @@ from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .const import CONF_ENDPOINT, DOMAIN, SET_SOUND_SETTING
from .const import CONF_ENDPOINT, DOMAIN, ERROR_REQUEST_RETRY, SET_SOUND_SETTING
_LOGGER = logging.getLogger(__name__)
@ -332,11 +332,27 @@ class SongpalEntity(MediaPlayerEntity):
async def async_turn_on(self) -> None:
"""Turn the device on."""
return await self._dev.set_power(True)
try:
return await self._dev.set_power(True)
except SongpalException as ex:
if ex.code == ERROR_REQUEST_RETRY:
_LOGGER.debug(
"Swallowing %s, the device might be already in the wanted state", ex
)
return
raise
async def async_turn_off(self) -> None:
"""Turn the device off."""
return await self._dev.set_power(False)
try:
return await self._dev.set_power(False)
except SongpalException as ex:
if ex.code == ERROR_REQUEST_RETRY:
_LOGGER.debug(
"Swallowing %s, the device might be already in the wanted state", ex
)
return
raise
async def async_mute_volume(self, mute: bool) -> None:
"""Mute or unmute the device."""

View File

@ -14,7 +14,10 @@ from songpal import (
from homeassistant.components import media_player, songpal
from homeassistant.components.media_player import MediaPlayerEntityFeature
from homeassistant.components.songpal.const import SET_SOUND_SETTING
from homeassistant.components.songpal.const import (
ERROR_REQUEST_RETRY,
SET_SOUND_SETTING,
)
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
@ -53,6 +56,15 @@ def _get_attributes(hass):
return state.as_dict()["attributes"]
async def _call(hass, service, **argv):
await hass.services.async_call(
media_player.DOMAIN,
service,
{"entity_id": ENTITY_ID, **argv},
blocking=True,
)
async def test_setup_platform(hass: HomeAssistant) -> None:
"""Test the legacy setup platform."""
mocked_device = _create_mocked_device(throw_exception=True)
@ -222,32 +234,24 @@ async def test_services(hass: HomeAssistant) -> None:
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
async def _call(service, **argv):
await hass.services.async_call(
media_player.DOMAIN,
service,
{"entity_id": ENTITY_ID, **argv},
blocking=True,
)
await _call(media_player.SERVICE_TURN_ON)
await _call(media_player.SERVICE_TURN_OFF)
await _call(media_player.SERVICE_TOGGLE)
await _call(hass, media_player.SERVICE_TURN_ON)
await _call(hass, media_player.SERVICE_TURN_OFF)
await _call(hass, media_player.SERVICE_TOGGLE)
assert mocked_device.set_power.call_count == 3
mocked_device.set_power.assert_has_calls([call(True), call(False), call(False)])
await _call(media_player.SERVICE_VOLUME_SET, volume_level=0.6)
await _call(media_player.SERVICE_VOLUME_UP)
await _call(media_player.SERVICE_VOLUME_DOWN)
await _call(hass, media_player.SERVICE_VOLUME_SET, volume_level=0.6)
await _call(hass, media_player.SERVICE_VOLUME_UP)
await _call(hass, media_player.SERVICE_VOLUME_DOWN)
assert mocked_device.volume1.set_volume.call_count == 3
mocked_device.volume1.set_volume.assert_has_calls([call(60), call(51), call(49)])
await _call(media_player.SERVICE_VOLUME_MUTE, is_volume_muted=True)
await _call(hass, media_player.SERVICE_VOLUME_MUTE, is_volume_muted=True)
mocked_device.volume1.set_mute.assert_called_once_with(True)
await _call(media_player.SERVICE_SELECT_SOURCE, source="none")
await _call(hass, media_player.SERVICE_SELECT_SOURCE, source="none")
mocked_device.input1.activate.assert_not_called()
await _call(media_player.SERVICE_SELECT_SOURCE, source="title1")
await _call(hass, media_player.SERVICE_SELECT_SOURCE, source="title1")
mocked_device.input1.activate.assert_called_once()
await hass.services.async_call(
@ -366,3 +370,33 @@ async def test_disconnected(
assert warning_records[0].message.endswith("Got disconnected, trying to reconnect")
assert warning_records[1].message.endswith("Connection reestablished")
assert not any(x.levelno == logging.ERROR for x in caplog.records)
@pytest.mark.parametrize(
"service", [media_player.SERVICE_TURN_ON, media_player.SERVICE_TURN_OFF]
)
@pytest.mark.parametrize(
("error_code", "swallow"), [(ERROR_REQUEST_RETRY, True), (1234, False)]
)
async def test_error_swallowing(hass, caplog, service, error_code, swallow):
"""Test swallowing specific errors on turn_on and turn_off."""
mocked_device = _create_mocked_device()
entry = MockConfigEntry(domain=songpal.DOMAIN, data=CONF_DATA)
entry.add_to_hass(hass)
with _patch_media_player_device(mocked_device):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
type(mocked_device).set_power = AsyncMock(
side_effect=[
SongpalException("Error to swallow", error=(error_code, "Error to swallow"))
]
)
if swallow:
await _call(hass, service)
assert "Swallowing" in caplog.text
else:
with pytest.raises(SongpalException):
await _call(hass, service)