Add JSON support to load_fixture (#88076)

* Add JSON support to load_fixture

* More tests

* Remove lru_cache on load_json
This commit is contained in:
epenet 2023-02-16 19:40:47 +01:00 committed by GitHub
parent bc2b35765e
commit 8c821c8969
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 104 additions and 34 deletions

View file

@ -18,6 +18,8 @@ JsonValueType = (
dict[str, "JsonValueType"] | list["JsonValueType"] | str | int | float | bool | None
)
"""Any data that can be returned by the standard JSON deserializing process."""
JsonArrayType = list[JsonValueType]
"""List that can be returned by the standard JSON deserializing process."""
JsonObjectType = dict[str, JsonValueType]
"""Dictionary that can be returned by the standard JSON deserializing process."""
@ -34,6 +36,15 @@ json_loads = orjson.loads
"""Parse JSON data."""
def json_loads_array(__obj: bytes | bytearray | memoryview | str) -> JsonArrayType:
"""Parse JSON data and ensure result is a list."""
value: JsonValueType = json_loads(__obj)
# Avoid isinstance overhead as we are not interested in list subclasses
if type(value) is list: # pylint: disable=unidiomatic-typecheck
return value
raise ValueError(f"Expected JSON to be parsed as a list got {type(value)}")
def json_loads_object(__obj: bytes | bytearray | memoryview | str) -> JsonObjectType:
"""Parse JSON data and ensure result is a dictionary."""
value: JsonValueType = json_loads(__obj)

View file

@ -67,6 +67,14 @@ from homeassistant.helpers.typing import ConfigType, StateType
from homeassistant.setup import setup_component
from homeassistant.util.async_ import run_callback_threadsafe
import homeassistant.util.dt as date_util
from homeassistant.util.json import (
JsonArrayType,
JsonObjectType,
JsonValueType,
json_loads,
json_loads_array,
json_loads_object,
)
from homeassistant.util.unit_system import METRIC_SYSTEM
import homeassistant.util.uuid as uuid_util
import homeassistant.util.yaml.loader as yaml_loader
@ -428,6 +436,27 @@ def load_fixture(filename: str, integration: str | None = None) -> str:
return get_fixture_path(filename, integration).read_text()
def load_json_value_fixture(
filename: str, integration: str | None = None
) -> JsonValueType:
"""Load a JSON value from a fixture."""
return json_loads(load_fixture(filename, integration))
def load_json_array_fixture(
filename: str, integration: str | None = None
) -> JsonArrayType:
"""Load a JSON array from a fixture."""
return json_loads_array(load_fixture(filename, integration))
def load_json_object_fixture(
filename: str, integration: str | None = None
) -> JsonObjectType:
"""Load a JSON object from a fixture."""
return json_loads_object(load_fixture(filename, integration))
def mock_state_change_event(
hass: HomeAssistant, new_state: State, old_state: State | None = None
) -> None:

View file

@ -1,10 +1,13 @@
"""Tests for AccuWeather."""
import json
from unittest.mock import PropertyMock, patch
from homeassistant.components.accuweather.const import DOMAIN
from tests.common import MockConfigEntry, load_fixture
from tests.common import (
MockConfigEntry,
load_json_array_fixture,
load_json_object_fixture,
)
async def init_integration(
@ -28,8 +31,8 @@ async def init_integration(
options=options,
)
current = json.loads(load_fixture("accuweather/current_conditions_data.json"))
forecast = json.loads(load_fixture("accuweather/forecast_data.json"))
current = load_json_object_fixture("accuweather/current_conditions_data.json")
forecast = load_json_array_fixture("accuweather/forecast_data.json")
if unsupported_icon:
current["WeatherIcon"] = 999

View file

@ -1,5 +1,4 @@
"""Define tests for the AccuWeather config flow."""
import json
from unittest.mock import PropertyMock, patch
from accuweather import ApiError, InvalidApiKeyError, RequestsExceededError
@ -10,7 +9,7 @@ from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry, load_fixture
from tests.common import MockConfigEntry, load_json_object_fixture
VALID_CONFIG = {
CONF_NAME: "abcd",
@ -99,7 +98,7 @@ async def test_integration_already_exists(hass: HomeAssistant) -> None:
"""Test we only allow a single config flow."""
with patch(
"homeassistant.components.accuweather.AccuWeather._async_get_data",
return_value=json.loads(load_fixture("accuweather/location_data.json")),
return_value=load_json_object_fixture("accuweather/location_data.json"),
):
MockConfigEntry(
domain=DOMAIN,
@ -121,7 +120,7 @@ async def test_create_entry(hass: HomeAssistant) -> None:
"""Test that the user step works."""
with patch(
"homeassistant.components.accuweather.AccuWeather._async_get_data",
return_value=json.loads(load_fixture("accuweather/location_data.json")),
return_value=load_json_object_fixture("accuweather/location_data.json"),
), patch(
"homeassistant.components.accuweather.async_setup_entry", return_value=True
):
@ -150,11 +149,11 @@ async def test_options_flow(hass: HomeAssistant) -> None:
with patch(
"homeassistant.components.accuweather.AccuWeather._async_get_data",
return_value=json.loads(load_fixture("accuweather/location_data.json")),
return_value=load_json_object_fixture("accuweather/location_data.json"),
), patch(
"homeassistant.components.accuweather.AccuWeather.async_get_current_conditions",
return_value=json.loads(
load_fixture("accuweather/current_conditions_data.json")
return_value=load_json_object_fixture(
"accuweather/current_conditions_data.json"
),
), patch(
"homeassistant.components.accuweather.AccuWeather.async_get_forecast"

View file

@ -1,11 +1,10 @@
"""Test AccuWeather diagnostics."""
import json
from homeassistant.core import HomeAssistant
from . import init_integration
from tests.common import load_fixture
from tests.common import load_json_object_fixture
from tests.components.diagnostics import get_diagnostics_for_config_entry
from tests.typing import ClientSessionGenerator
@ -16,9 +15,10 @@ async def test_entry_diagnostics(
"""Test config entry diagnostics."""
entry = await init_integration(hass)
coordinator_data = json.loads(
load_fixture("current_conditions_data.json", "accuweather")
coordinator_data = load_json_object_fixture(
"current_conditions_data.json", "accuweather"
)
coordinator_data["forecast"] = {}
result = await get_diagnostics_for_config_entry(hass, hass_client, entry)

View file

@ -1,6 +1,5 @@
"""Test init of AccuWeather integration."""
from datetime import timedelta
import json
from unittest.mock import patch
from accuweather import ApiError
@ -13,7 +12,12 @@ from homeassistant.util.dt import utcnow
from . import init_integration
from tests.common import MockConfigEntry, async_fire_time_changed, load_fixture
from tests.common import (
MockConfigEntry,
async_fire_time_changed,
load_json_array_fixture,
load_json_object_fixture,
)
async def test_async_setup_entry(hass: HomeAssistant) -> None:
@ -69,7 +73,7 @@ async def test_update_interval(hass: HomeAssistant) -> None:
assert entry.state is ConfigEntryState.LOADED
current = json.loads(load_fixture("accuweather/current_conditions_data.json"))
current = load_json_object_fixture("accuweather/current_conditions_data.json")
future = utcnow() + timedelta(minutes=40)
with patch(
@ -90,8 +94,8 @@ async def test_update_interval_forecast(hass: HomeAssistant) -> None:
assert entry.state is ConfigEntryState.LOADED
current = json.loads(load_fixture("accuweather/current_conditions_data.json"))
forecast = json.loads(load_fixture("accuweather/forecast_data.json"))
current = load_json_object_fixture("accuweather/current_conditions_data.json")
forecast = load_json_array_fixture("accuweather/forecast_data.json")
future = utcnow() + timedelta(minutes=80)
with patch(

View file

@ -1,6 +1,5 @@
"""Test sensor of AccuWeather integration."""
from datetime import timedelta
import json
from unittest.mock import PropertyMock, patch
from homeassistant.components.accuweather.const import ATTRIBUTION, DOMAIN
@ -35,7 +34,11 @@ from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM
from . import init_integration
from tests.common import async_fire_time_changed, load_fixture
from tests.common import (
async_fire_time_changed,
load_json_array_fixture,
load_json_object_fixture,
)
async def test_sensor_without_forecast(hass: HomeAssistant) -> None:
@ -684,8 +687,8 @@ async def test_availability(hass: HomeAssistant) -> None:
future = utcnow() + timedelta(minutes=120)
with patch(
"homeassistant.components.accuweather.AccuWeather.async_get_current_conditions",
return_value=json.loads(
load_fixture("accuweather/current_conditions_data.json")
return_value=load_json_object_fixture(
"accuweather/current_conditions_data.json"
),
), patch(
"homeassistant.components.accuweather.AccuWeather.requests_remaining",
@ -707,8 +710,8 @@ async def test_manual_update_entity(hass: HomeAssistant) -> None:
await async_setup_component(hass, "homeassistant", {})
current = json.loads(load_fixture("accuweather/current_conditions_data.json"))
forecast = json.loads(load_fixture("accuweather/forecast_data.json"))
current = load_json_object_fixture("accuweather/current_conditions_data.json")
forecast = load_json_array_fixture("accuweather/forecast_data.json")
with patch(
"homeassistant.components.accuweather.AccuWeather.async_get_current_conditions",
@ -755,8 +758,8 @@ async def test_state_update(hass: HomeAssistant) -> None:
future = utcnow() + timedelta(minutes=60)
current_condition = json.loads(
load_fixture("accuweather/current_conditions_data.json")
current_condition = load_json_object_fixture(
"accuweather/current_conditions_data.json"
)
current_condition["Ceiling"]["Metric"]["Value"] = 3300

View file

@ -1,6 +1,5 @@
"""Test weather of AccuWeather integration."""
from datetime import timedelta
import json
from unittest.mock import PropertyMock, patch
from homeassistant.components.accuweather.const import ATTRIBUTION
@ -30,7 +29,11 @@ from homeassistant.util.dt import utcnow
from . import init_integration
from tests.common import async_fire_time_changed, load_fixture
from tests.common import (
async_fire_time_changed,
load_json_array_fixture,
load_json_object_fixture,
)
async def test_weather_without_forecast(hass: HomeAssistant) -> None:
@ -111,8 +114,8 @@ async def test_availability(hass: HomeAssistant) -> None:
future = utcnow() + timedelta(minutes=120)
with patch(
"homeassistant.components.accuweather.AccuWeather.async_get_current_conditions",
return_value=json.loads(
load_fixture("accuweather/current_conditions_data.json")
return_value=load_json_object_fixture(
"accuweather/current_conditions_data.json"
),
), patch(
"homeassistant.components.accuweather.AccuWeather.requests_remaining",
@ -134,8 +137,8 @@ async def test_manual_update_entity(hass: HomeAssistant) -> None:
await async_setup_component(hass, "homeassistant", {})
current = json.loads(load_fixture("accuweather/current_conditions_data.json"))
forecast = json.loads(load_fixture("accuweather/forecast_data.json"))
current = load_json_object_fixture("accuweather/current_conditions_data.json")
forecast = load_json_array_fixture("accuweather/forecast_data.json")
with patch(
"homeassistant.components.accuweather.AccuWeather.async_get_current_conditions",

View file

@ -15,6 +15,7 @@ from homeassistant.helpers.json import JSONEncoder as DefaultHASSJSONEncoder
from homeassistant.util.json import (
SerializationError,
find_paths_unserializable_data,
json_loads_array,
json_loads_object,
load_json,
save_json,
@ -194,6 +195,23 @@ def test_find_unserializable_data() -> None:
) == {"$(BadData).bla": bad_data}
def test_json_loads_array() -> None:
"""Test json_loads_array validates result."""
assert json_loads_array('[{"c":1.2}]') == [{"c": 1.2}]
with pytest.raises(
ValueError, match="Expected JSON to be parsed as a list got <class 'dict'>"
):
json_loads_array("{}")
with pytest.raises(
ValueError, match="Expected JSON to be parsed as a list got <class 'bool'>"
):
json_loads_array("true")
with pytest.raises(
ValueError, match="Expected JSON to be parsed as a list got <class 'NoneType'>"
):
json_loads_array("null")
def test_json_loads_object() -> None:
"""Test json_loads_object validates result."""
assert json_loads_object('{"c":1.2}') == {"c": 1.2}