Merge otto/ha-client-errors into main

This commit is contained in:
2026-06-10 23:29:30 +02:00
22 changed files with 331 additions and 93 deletions

View File

@@ -1,60 +1,71 @@
from __future__ import annotations
from unittest.mock import MagicMock, patch
from unittest.mock import Mock
import pytest
import requests
from app.ha.client import HaClient
from app.ha.exceptions import HaAuthError, HaHttpError, HaTimeoutError, HaUnexpectedPayloadError
from tests.helpers import build_ha_client_settings
from app.ha.client import HaClient, HaClientSettings
from app.ha.exceptions import (
HaAuthError,
HaHttpError,
HaTimeoutError,
HaUnexpectedPayloadError,
)
@pytest.fixture()
def client():
settings = build_ha_client_settings()
return HaClient(settings)
def _client_with_response(response: Mock) -> HaClient:
client = HaClient(HaClientSettings(url="http://ha.local", token="test-token"))
client._session.get = Mock(return_value=response) # type: ignore[method-assign]
return client
def test_list_entities_timeout(client):
with patch.object(client._session, "get", side_effect=requests.Timeout("t")):
with pytest.raises(HaTimeoutError):
client.list_entities()
def _response(status_code: int = 200, payload: object | None = None) -> Mock:
response = Mock()
response.status_code = status_code
response.json.return_value = [] if payload is None else payload
if status_code >= 400:
response.raise_for_status.side_effect = requests.HTTPError("upstream failed")
return response
def test_list_entities_auth_error(client):
response = MagicMock()
response.status_code = 401
response.raise_for_status = MagicMock()
with patch.object(client._session, "get", return_value=response):
with pytest.raises(HaAuthError):
client.list_entities()
def test_list_entities_returns_home_assistant_payload() -> None:
payload = [{"entity_id": "sensor.temperature", "state": "21"}]
client = _client_with_response(_response(payload=payload))
assert client.list_entities() == payload
def test_list_entities_http_error(client):
response = MagicMock()
response.status_code = 502
response.raise_for_status = MagicMock(side_effect=requests.HTTPError("bad"))
with patch.object(client._session, "get", return_value=response):
with pytest.raises(HaHttpError):
client.list_entities()
def test_list_entities_maps_timeout() -> None:
client = HaClient(HaClientSettings(url="http://ha.local", token="test-token"))
client._session.get = Mock(side_effect=requests.Timeout("timed out")) # type: ignore[method-assign]
with pytest.raises(HaTimeoutError):
client.list_entities()
def test_list_entities_invalid_json(client):
response = MagicMock()
response.status_code = 200
response.raise_for_status = MagicMock()
response.json = MagicMock(side_effect=ValueError("invalid json"))
with patch.object(client._session, "get", return_value=response):
with pytest.raises(HaUnexpectedPayloadError):
client.list_entities()
@pytest.mark.parametrize("status_code", [401, 403])
def test_list_entities_maps_auth_errors(status_code: int) -> None:
client = _client_with_response(_response(status_code=status_code))
with pytest.raises(HaAuthError) as exc_info:
client.list_entities()
assert exc_info.value.status_code == status_code
def test_list_entities_wrong_payload_type(client):
response = MagicMock()
response.status_code = 200
response.raise_for_status = MagicMock()
response.json = MagicMock(return_value={"data": []})
with patch.object(client._session, "get", return_value=response):
with pytest.raises(HaUnexpectedPayloadError):
client.list_entities()
def test_list_entities_maps_http_errors() -> None:
client = _client_with_response(_response(status_code=500))
with pytest.raises(HaHttpError) as exc_info:
client.list_entities()
assert exc_info.value.status_code == 500
def test_list_entities_rejects_invalid_json() -> None:
response = _response()
response.json.side_effect = ValueError("not json")
client = _client_with_response(response)
with pytest.raises(HaUnexpectedPayloadError):
client.list_entities()
def test_list_entities_rejects_non_list_payload() -> None:
client = _client_with_response(_response(payload={"entity_id": "sensor.temperature"}))
with pytest.raises(HaUnexpectedPayloadError):
client.list_entities()