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
3d3f2527cb
commit
672ffa5984
@ -115,6 +115,16 @@ class RestData:
|
||||
for key, value in rendered_params.items():
|
||||
if isinstance(value, bool):
|
||||
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)
|
||||
# Create request kwargs
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""The tests for the REST sensor platform."""
|
||||
|
||||
from http import HTTPStatus
|
||||
import logging
|
||||
import ssl
|
||||
from unittest.mock import patch
|
||||
|
||||
@ -19,6 +20,14 @@ from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_ENTITY_ID,
|
||||
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,
|
||||
SERVICE_RELOAD,
|
||||
STATE_UNAVAILABLE,
|
||||
@ -1066,6 +1075,124 @@ async def test_update_with_failed_get(
|
||||
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:
|
||||
"""Verify we can reload reset sensors."""
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user