|
1 | 1 | """The tests for the REST sensor platform.""" |
2 | 2 |
|
3 | 3 | from http import HTTPStatus |
| 4 | +import logging |
4 | 5 | import ssl |
5 | 6 | from unittest.mock import patch |
6 | 7 |
|
|
19 | 20 | ATTR_DEVICE_CLASS, |
20 | 21 | ATTR_ENTITY_ID, |
21 | 22 | ATTR_UNIT_OF_MEASUREMENT, |
| 23 | + CONF_DEVICE_CLASS, |
| 24 | + CONF_FORCE_UPDATE, |
| 25 | + CONF_METHOD, |
| 26 | + CONF_NAME, |
| 27 | + CONF_PARAMS, |
| 28 | + CONF_RESOURCE, |
| 29 | + CONF_UNIT_OF_MEASUREMENT, |
| 30 | + CONF_VALUE_TEMPLATE, |
22 | 31 | CONTENT_TYPE_JSON, |
23 | 32 | SERVICE_RELOAD, |
24 | 33 | STATE_UNAVAILABLE, |
@@ -1066,6 +1075,124 @@ async def test_update_with_failed_get( |
1066 | 1075 | assert "Empty reply" in caplog.text |
1067 | 1076 |
|
1068 | 1077 |
|
| 1078 | +async def test_query_param_dict_value( |
| 1079 | + hass: HomeAssistant, |
| 1080 | + caplog: pytest.LogCaptureFixture, |
| 1081 | + aioclient_mock: AiohttpClientMocker, |
| 1082 | +) -> None: |
| 1083 | + """Test dict values in query params are handled for backward compatibility.""" |
| 1084 | + # Mock response |
| 1085 | + aioclient_mock.post( |
| 1086 | + "https://www.envertecportal.com/ApiInverters/QueryTerminalReal", |
| 1087 | + status=HTTPStatus.OK, |
| 1088 | + json={"Data": {"QueryResults": [{"POWER": 1500}]}}, |
| 1089 | + ) |
| 1090 | + |
| 1091 | + # This test checks that when template_complex processes a string that looks like |
| 1092 | + # a dict/list, it converts it to an actual dict/list, which then needs to be |
| 1093 | + # handled by our backward compatibility code |
| 1094 | + with caplog.at_level(logging.DEBUG, logger="homeassistant.components.rest.data"): |
| 1095 | + assert await async_setup_component( |
| 1096 | + hass, |
| 1097 | + DOMAIN, |
| 1098 | + { |
| 1099 | + DOMAIN: [ |
| 1100 | + { |
| 1101 | + CONF_RESOURCE: ( |
| 1102 | + "https://www.envertecportal.com/ApiInverters/" |
| 1103 | + "QueryTerminalReal" |
| 1104 | + ), |
| 1105 | + CONF_METHOD: "POST", |
| 1106 | + CONF_PARAMS: { |
| 1107 | + "page": "1", |
| 1108 | + "perPage": "20", |
| 1109 | + "orderBy": "SN", |
| 1110 | + # When processed by template.render_complex, certain |
| 1111 | + # strings might be converted to dicts/lists if they |
| 1112 | + # look like JSON |
| 1113 | + "whereCondition": ( |
| 1114 | + "{{ {'STATIONID': 'A6327A17797C1234'} }}" |
| 1115 | + ), # Template that evaluates to dict |
| 1116 | + }, |
| 1117 | + "sensor": [ |
| 1118 | + { |
| 1119 | + CONF_NAME: "Solar MPPT1 Power", |
| 1120 | + CONF_VALUE_TEMPLATE: ( |
| 1121 | + "{{ value_json.Data.QueryResults[0].POWER }}" |
| 1122 | + ), |
| 1123 | + CONF_DEVICE_CLASS: "power", |
| 1124 | + CONF_UNIT_OF_MEASUREMENT: "W", |
| 1125 | + CONF_FORCE_UPDATE: True, |
| 1126 | + "state_class": "measurement", |
| 1127 | + } |
| 1128 | + ], |
| 1129 | + } |
| 1130 | + ] |
| 1131 | + }, |
| 1132 | + ) |
| 1133 | + await hass.async_block_till_done() |
| 1134 | + |
| 1135 | + # The sensor should be created successfully with backward compatibility |
| 1136 | + assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1 |
| 1137 | + state = hass.states.get("sensor.solar_mppt1_power") |
| 1138 | + assert state is not None |
| 1139 | + assert state.state == "1500" |
| 1140 | + |
| 1141 | + # Check that a debug message was logged about the parameter conversion |
| 1142 | + assert "REST query parameter 'whereCondition' has type" in caplog.text |
| 1143 | + assert "converting to string" in caplog.text |
| 1144 | + |
| 1145 | + |
| 1146 | +async def test_query_param_json_string_preserved( |
| 1147 | + hass: HomeAssistant, |
| 1148 | + aioclient_mock: AiohttpClientMocker, |
| 1149 | +) -> None: |
| 1150 | + """Test that JSON strings in query params are preserved and not converted to dicts.""" |
| 1151 | + # Mock response |
| 1152 | + aioclient_mock.get( |
| 1153 | + "https://api.example.com/data", |
| 1154 | + status=HTTPStatus.OK, |
| 1155 | + json={"value": 42}, |
| 1156 | + ) |
| 1157 | + |
| 1158 | + # Config with JSON string (quoted) - should remain a string |
| 1159 | + assert await async_setup_component( |
| 1160 | + hass, |
| 1161 | + DOMAIN, |
| 1162 | + { |
| 1163 | + DOMAIN: [ |
| 1164 | + { |
| 1165 | + CONF_RESOURCE: "https://api.example.com/data", |
| 1166 | + CONF_METHOD: "GET", |
| 1167 | + CONF_PARAMS: { |
| 1168 | + "filter": '{"type": "sensor", "id": 123}', # JSON string |
| 1169 | + "normal": "value", |
| 1170 | + }, |
| 1171 | + "sensor": [ |
| 1172 | + { |
| 1173 | + CONF_NAME: "Test Sensor", |
| 1174 | + CONF_VALUE_TEMPLATE: "{{ value_json.value }}", |
| 1175 | + } |
| 1176 | + ], |
| 1177 | + } |
| 1178 | + ] |
| 1179 | + }, |
| 1180 | + ) |
| 1181 | + await hass.async_block_till_done() |
| 1182 | + |
| 1183 | + # Check the sensor was created |
| 1184 | + assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1 |
| 1185 | + state = hass.states.get("sensor.test_sensor") |
| 1186 | + assert state is not None |
| 1187 | + assert state.state == "42" |
| 1188 | + |
| 1189 | + # Verify the request was made with the JSON string intact |
| 1190 | + assert len(aioclient_mock.mock_calls) == 1 |
| 1191 | + method, url, data, headers = aioclient_mock.mock_calls[0] |
| 1192 | + assert url.query["filter"] == '{"type": "sensor", "id": 123}' |
| 1193 | + assert url.query["normal"] == "value" |
| 1194 | + |
| 1195 | + |
1069 | 1196 | async def test_reload(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) -> None: |
1070 | 1197 | """Verify we can reload reset sensors.""" |
1071 | 1198 |
|
|
0 commit comments