Fix changes in Xbox API error response (#182100)

This commit is contained in:
Manu
2026-09-14 18:41:05 +02:00
committed by GitHub
parent 02c0d2eb86
commit 1bdf333470
8 changed files with 184 additions and 39 deletions
+10 -10
View File
@@ -2,7 +2,6 @@
from collections.abc import Awaitable, Callable, Coroutine
from functools import wraps
from http import HTTPStatus
import logging
from typing import Any, Concatenate, override
@@ -191,15 +190,16 @@ class XboxMediaPlayer(XboxConsoleBaseEntity, MediaPlayerEntity):
@override
async def async_turn_on(self) -> None:
"""Turn the media player on."""
try:
await self.client.smartglass.wake_up(self._console.id)
except HTTPStatusError as e:
if e.response.status_code == HTTPStatus.NOT_FOUND:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="turn_on_failed",
) from e
raise
if (
err := await self.client.smartglass.wake_up(self._console.id)
).status.error_code != "OK":
_LOGGER.debug(
"Xbox error: %s (%s)", err.status.error_message, err.status.error_code
)
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="turn_on_failed",
)
@exception_handler
@override
+35 -15
View File
@@ -3,13 +3,16 @@
import asyncio
from collections.abc import Awaitable, Callable, Coroutine, Iterable
from functools import wraps
from http import HTTPStatus
import logging
from typing import Any, Concatenate, override
from httpx import HTTPStatusError, RequestError, TimeoutException
from pythonxbox.api.provider.smartglass import SmartglassProvider
from pythonxbox.api.provider.smartglass.models import InputKeyType, PowerState
from pythonxbox.api.provider.smartglass.models import (
CommandResponse,
InputKeyType,
PowerState,
)
from homeassistant.components.remote import (
ATTR_DELAY_SECS,
@@ -29,7 +32,12 @@ _LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 1
MAP_COMMAND: dict[str, Callable[[SmartglassProvider], Callable]] = {
MAP_COMMAND: dict[
str,
Callable[
[SmartglassProvider], Callable[[str], Coroutine[Any, Any, CommandResponse]]
],
] = {
"WakeUp": lambda x: x.wake_up,
"TurnOff": lambda x: x.turn_off,
"Reboot": lambda x: x.reboot,
@@ -120,15 +128,16 @@ class XboxRemote(XboxConsoleBaseEntity, RemoteEntity):
@override
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the Xbox on."""
try:
await self.client.smartglass.wake_up(self._console.id)
except HTTPStatusError as e:
if e.response.status_code == HTTPStatus.NOT_FOUND:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="turn_on_failed",
) from e
raise
if (
err := await self.client.smartglass.wake_up(self._console.id)
).status.error_code != "OK":
_LOGGER.debug(
"Xbox error: %s (%s)", err.status.error_message, err.status.error_code
)
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="turn_on_failed",
)
@exception_handler
@override
@@ -149,9 +158,20 @@ class XboxRemote(XboxConsoleBaseEntity, RemoteEntity):
button = InputKeyType(single_command)
await self.client.smartglass.press_button(self._console.id, button)
elif single_command in MAP_COMMAND:
await MAP_COMMAND[single_command](self.client.smartglass)(
self._console.id
)
if (
err := await MAP_COMMAND[single_command](
self.client.smartglass
)(self._console.id)
).status.error_code != "OK":
_LOGGER.debug(
"Xbox error: %s (%s)",
err.status.error_message,
err.status.error_code,
)
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="command_failed",
)
else:
await self.client.smartglass.insert_text(
self._console.id, single_command.removeprefix("text:")
@@ -156,6 +156,9 @@
"auth_exception": {
"message": "Xbox Network authentication failed, re-authentication required"
},
"command_failed": {
"message": "Command failed."
},
"media_not_found": {
"message": "The requested media could not be found."
},
+17
View File
@@ -10,6 +10,7 @@ from pythonxbox.api.provider.gameclips.models import GameclipsResponse
from pythonxbox.api.provider.people.models import PeopleResponse
from pythonxbox.api.provider.screenshots.models import ScreenshotResponse
from pythonxbox.api.provider.smartglass.models import (
CommandResponse,
InstalledPackagesList,
SmartglassConsoleList,
SmartglassConsoleStatus,
@@ -153,6 +154,22 @@ def mock_xbox_live_client() -> Generator[AsyncMock]:
client.smartglass.get_installed_apps.return_value = InstalledPackagesList(
**load_json_object_fixture("smartglass_installed_applications.json", DOMAIN)
)
command_response = CommandResponse(
**load_json_object_fixture("smartglass_command_response.json", DOMAIN)
)
client.smartglass.wake_up.return_value = command_response
client.smartglass.turn_off.return_value = command_response
client.smartglass.reboot.return_value = command_response
client.smartglass.mute.return_value = command_response
client.smartglass.unmute.return_value = command_response
client.smartglass.play.return_value = command_response
client.smartglass.pause.return_value = command_response
client.smartglass.previous.return_value = command_response
client.smartglass.next.return_value = command_response
client.smartglass.go_home.return_value = command_response
client.smartglass.go_back.return_value = command_response
client.smartglass.show_guide_tab.return_value = command_response
client.smartglass.show_tv_guide.return_value = command_response
client.catalog = AsyncMock()
client.catalog.get_product_from_alternate_id.return_value = CatalogResponse(
@@ -0,0 +1,17 @@
{
"result": null,
"uiText": null,
"destination": {
"id": "AAAAAAAAAAAAAAAA",
"name": "Xbox One S",
"powerState": "ConnectedStandby",
"consoleType": "XboxOneS",
"osVersion": "10.0.26100.9426"
},
"userInfo": null,
"opId": "11111111111111111111111111111111",
"status": {
"errorCode": "OK",
"errorMessage": null
}
}
@@ -0,0 +1,11 @@
{
"result": null,
"uiText": null,
"destination": null,
"userInfo": null,
"opId": "1111111111111111111111",
"status": {
"errorCode": "ErrorCallingWNS",
"errorMessage": "Send command failed"
}
}
+5 -5
View File
@@ -1,7 +1,6 @@
"""Test the Xbox media_player platform."""
from collections.abc import Generator
from http import HTTPStatus
from typing import Any
from unittest.mock import patch
@@ -9,6 +8,7 @@ from httpx import HTTPStatusError, RequestError, TimeoutException
import pytest
from pythonxbox.api.provider.catalog.models import CatalogResponse
from pythonxbox.api.provider.smartglass.models import (
CommandResponse,
SmartglassConsoleStatus,
VolumeDirection,
)
@@ -320,10 +320,10 @@ async def test_media_player_turn_on_failed(
assert config_entry.state is ConfigEntryState.LOADED
xbox_live_client.smartglass.wake_up.side_effect = (
HTTPStatusError(
"", request=Mock(), response=Mock(status_code=HTTPStatus.NOT_FOUND)
),
xbox_live_client.smartglass.wake_up.return_value = CommandResponse(
**await async_load_json_object_fixture(
hass, "smartglass_command_response_error.json", DOMAIN
) # type: ignore[reportArgumentType]
)
with pytest.raises(HomeAssistantError) as e:
+86 -9
View File
@@ -1,12 +1,11 @@
"""Test the Xbox remote platform."""
from collections.abc import Generator
from http import HTTPStatus
from unittest.mock import AsyncMock, patch
from httpx import HTTPStatusError, RequestError, TimeoutException
import pytest
from pythonxbox.api.provider.smartglass.models import InputKeyType
from pythonxbox.api.provider.smartglass.models import CommandResponse, InputKeyType
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.remote import (
@@ -14,6 +13,7 @@ from homeassistant.components.remote import (
DOMAIN as REMOTE_DOMAIN,
SERVICE_SEND_COMMAND,
)
from homeassistant.components.xbox.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import (
ATTR_COMMAND,
@@ -26,7 +26,12 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
from tests.common import Mock, MockConfigEntry, snapshot_platform
from tests.common import (
Mock,
MockConfigEntry,
async_load_json_object_fixture,
snapshot_platform,
)
@pytest.fixture(autouse=True)
@@ -262,18 +267,61 @@ async def test_send_command_exceptions(
assert e.value.translation_key == translation_key
@pytest.mark.parametrize(
("command", "call_method"),
[
("WakeUp", "wake_up"),
("TurnOff", "turn_off"),
("Reboot", "reboot"),
("Mute", "mute"),
("Unmute", "unmute"),
("Play", "play"),
("Pause", "pause"),
("Previous", "previous"),
("Next", "next"),
("GoHome", "go_home"),
("GoBack", "go_back"),
("ShowGuideTab", "show_guide_tab"),
("ShowGuide", "show_tv_guide"),
],
)
async def test_send_command_failed(
hass: HomeAssistant,
xbox_live_client: AsyncMock,
config_entry: MockConfigEntry,
command: str,
call_method: str,
) -> None:
"""Test remote send command failed error."""
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
getattr(xbox_live_client.smartglass, call_method).return_value = CommandResponse(
**await async_load_json_object_fixture(
hass, "smartglass_command_response_error.json", DOMAIN
) # type: ignore[reportArgumentType]
)
with pytest.raises(HomeAssistantError) as e:
await hass.services.async_call(
REMOTE_DOMAIN,
SERVICE_SEND_COMMAND,
{ATTR_COMMAND: command, ATTR_DELAY_SECS: 0},
target={ATTR_ENTITY_ID: "remote.xone"},
blocking=True,
)
assert e.value.translation_key == "command_failed"
@pytest.mark.parametrize(
("exception", "translation_key"),
[
(TimeoutException(""), "timeout_exception"),
(RequestError("", request=Mock()), "request_exception"),
(HTTPStatusError("", request=Mock(), response=Mock()), "request_exception"),
(
HTTPStatusError(
"", request=Mock(), response=Mock(status_code=HTTPStatus.NOT_FOUND)
),
"turn_on_failed",
),
],
)
async def test_turn_on_exceptions(
@@ -292,6 +340,7 @@ async def test_turn_on_exceptions(
assert config_entry.state is ConfigEntryState.LOADED
xbox_live_client.smartglass.wake_up.side_effect = exception
with pytest.raises(HomeAssistantError) as e:
await hass.services.async_call(
REMOTE_DOMAIN,
@@ -302,6 +351,34 @@ async def test_turn_on_exceptions(
assert e.value.translation_key == translation_key
async def test_turn_on_failed(
hass: HomeAssistant,
xbox_live_client: AsyncMock,
config_entry: MockConfigEntry,
) -> None:
"""Test remote turn on exceptions."""
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
xbox_live_client.smartglass.wake_up.return_value = CommandResponse(
**await async_load_json_object_fixture(
hass, "smartglass_command_response_error.json", DOMAIN
) # type: ignore[reportArgumentType]
)
with pytest.raises(HomeAssistantError) as e:
await hass.services.async_call(
REMOTE_DOMAIN,
SERVICE_TURN_ON,
target={ATTR_ENTITY_ID: "remote.xone"},
blocking=True,
)
assert e.value.translation_key == "turn_on_failed"
@pytest.mark.parametrize(
("exception", "translation_key"),
[