mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 15:47:12 +00:00
Restore httpx compatibility for non-primitive REST query parameters (#148286)
This commit is contained in:
parent
b71bcb002b
commit
03e295ace0
@ -115,6 +115,16 @@ class RestData:
|
|||||||
for key, value in rendered_params.items():
|
for key, value in rendered_params.items():
|
||||||
if isinstance(value, bool):
|
if isinstance(value, bool):
|
||||||
rendered_params[key] = str(value).lower()
|
rendered_params[key] = str(value).lower()
|
||||||
|
elif not isinstance(value, (str, int, float, type(None))):
|
||||||
|
# For backward compatibility with httpx behavior, convert non-primitive
|
||||||
|
# types to strings. This maintains compatibility after switching from
|
||||||
|
# httpx to aiohttp. See https://github.com/home-assistant/core/issues/148153
|
||||||
|
_LOGGER.debug(
|
||||||
|
"REST query parameter '%s' has type %s, converting to string",
|
||||||
|
key,
|
||||||
|
type(value).__name__,
|
||||||
|
)
|
||||||
|
rendered_params[key] = str(value)
|
||||||
|
|
||||||
_LOGGER.debug("Updating from %s", self._resource)
|
_LOGGER.debug("Updating from %s", self._resource)
|
||||||
# Create request kwargs
|
# Create request kwargs
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""The tests for the REST sensor platform."""
|
"""The tests for the REST sensor platform."""
|
||||||
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
import logging
|
||||||
import ssl
|
import ssl
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
@ -19,6 +20,14 @@ from homeassistant.const import (
|
|||||||
ATTR_DEVICE_CLASS,
|
ATTR_DEVICE_CLASS,
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
ATTR_UNIT_OF_MEASUREMENT,
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
|
CONF_DEVICE_CLASS,
|
||||||
|
CONF_FORCE_UPDATE,
|
||||||
|
CONF_METHOD,
|
||||||
|
CONF_NAME,
|
||||||
|
CONF_PARAMS,
|
||||||
|
CONF_RESOURCE,
|
||||||
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
|
CONF_VALUE_TEMPLATE,
|
||||||
CONTENT_TYPE_JSON,
|
CONTENT_TYPE_JSON,
|
||||||
SERVICE_RELOAD,
|
SERVICE_RELOAD,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
@ -978,6 +987,124 @@ async def test_update_with_failed_get(
|
|||||||
assert "Empty reply" in caplog.text
|
assert "Empty reply" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_query_param_dict_value(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
aioclient_mock: AiohttpClientMocker,
|
||||||
|
) -> None:
|
||||||
|
"""Test dict values in query params are handled for backward compatibility."""
|
||||||
|
# Mock response
|
||||||
|
aioclient_mock.post(
|
||||||
|
"https://www.envertecportal.com/ApiInverters/QueryTerminalReal",
|
||||||
|
status=HTTPStatus.OK,
|
||||||
|
json={"Data": {"QueryResults": [{"POWER": 1500}]}},
|
||||||
|
)
|
||||||
|
|
||||||
|
# This test checks that when template_complex processes a string that looks like
|
||||||
|
# a dict/list, it converts it to an actual dict/list, which then needs to be
|
||||||
|
# handled by our backward compatibility code
|
||||||
|
with caplog.at_level(logging.DEBUG, logger="homeassistant.components.rest.data"):
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
{
|
||||||
|
DOMAIN: [
|
||||||
|
{
|
||||||
|
CONF_RESOURCE: (
|
||||||
|
"https://www.envertecportal.com/ApiInverters/"
|
||||||
|
"QueryTerminalReal"
|
||||||
|
),
|
||||||
|
CONF_METHOD: "POST",
|
||||||
|
CONF_PARAMS: {
|
||||||
|
"page": "1",
|
||||||
|
"perPage": "20",
|
||||||
|
"orderBy": "SN",
|
||||||
|
# When processed by template.render_complex, certain
|
||||||
|
# strings might be converted to dicts/lists if they
|
||||||
|
# look like JSON
|
||||||
|
"whereCondition": (
|
||||||
|
"{{ {'STATIONID': 'A6327A17797C1234'} }}"
|
||||||
|
), # Template that evaluates to dict
|
||||||
|
},
|
||||||
|
"sensor": [
|
||||||
|
{
|
||||||
|
CONF_NAME: "Solar MPPT1 Power",
|
||||||
|
CONF_VALUE_TEMPLATE: (
|
||||||
|
"{{ value_json.Data.QueryResults[0].POWER }}"
|
||||||
|
),
|
||||||
|
CONF_DEVICE_CLASS: "power",
|
||||||
|
CONF_UNIT_OF_MEASUREMENT: "W",
|
||||||
|
CONF_FORCE_UPDATE: True,
|
||||||
|
"state_class": "measurement",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# The sensor should be created successfully with backward compatibility
|
||||||
|
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
|
||||||
|
state = hass.states.get("sensor.solar_mppt1_power")
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == "1500"
|
||||||
|
|
||||||
|
# Check that a debug message was logged about the parameter conversion
|
||||||
|
assert "REST query parameter 'whereCondition' has type" in caplog.text
|
||||||
|
assert "converting to string" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_query_param_json_string_preserved(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
aioclient_mock: AiohttpClientMocker,
|
||||||
|
) -> None:
|
||||||
|
"""Test that JSON strings in query params are preserved and not converted to dicts."""
|
||||||
|
# Mock response
|
||||||
|
aioclient_mock.get(
|
||||||
|
"https://api.example.com/data",
|
||||||
|
status=HTTPStatus.OK,
|
||||||
|
json={"value": 42},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Config with JSON string (quoted) - should remain a string
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
{
|
||||||
|
DOMAIN: [
|
||||||
|
{
|
||||||
|
CONF_RESOURCE: "https://api.example.com/data",
|
||||||
|
CONF_METHOD: "GET",
|
||||||
|
CONF_PARAMS: {
|
||||||
|
"filter": '{"type": "sensor", "id": 123}', # JSON string
|
||||||
|
"normal": "value",
|
||||||
|
},
|
||||||
|
"sensor": [
|
||||||
|
{
|
||||||
|
CONF_NAME: "Test Sensor",
|
||||||
|
CONF_VALUE_TEMPLATE: "{{ value_json.value }}",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Check the sensor was created
|
||||||
|
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
|
||||||
|
state = hass.states.get("sensor.test_sensor")
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == "42"
|
||||||
|
|
||||||
|
# Verify the request was made with the JSON string intact
|
||||||
|
assert len(aioclient_mock.mock_calls) == 1
|
||||||
|
method, url, data, headers = aioclient_mock.mock_calls[0]
|
||||||
|
assert url.query["filter"] == '{"type": "sensor", "id": 123}'
|
||||||
|
assert url.query["normal"] == "value"
|
||||||
|
|
||||||
|
|
||||||
async def test_reload(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) -> None:
|
async def test_reload(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) -> None:
|
||||||
"""Verify we can reload reset sensors."""
|
"""Verify we can reload reset sensors."""
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user