Add tests for renault QuotaLimitException (#141985)

This commit is contained in:
epenet 2025-04-01 11:01:13 +02:00 committed by GitHub
parent 28c38e92d4
commit 3155c1cd4f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 144 additions and 114 deletions

View File

@ -1,9 +1,8 @@
"""Provide common Renault fixtures."""
from collections.abc import Generator, Iterator
from collections.abc import AsyncGenerator, Generator
import contextlib
from types import MappingProxyType
from typing import Any
from unittest.mock import AsyncMock, patch
import pytest
@ -51,7 +50,7 @@ def get_config_entry(hass: HomeAssistant) -> ConfigEntry:
@pytest.fixture(name="patch_renault_account")
async def patch_renault_account(hass: HomeAssistant) -> RenaultAccount:
async def patch_renault_account(hass: HomeAssistant) -> AsyncGenerator[RenaultAccount]:
"""Create a Renault account."""
renault_account = RenaultAccount(
MOCK_ACCOUNT_ID,
@ -68,7 +67,7 @@ async def patch_renault_account(hass: HomeAssistant) -> RenaultAccount:
@pytest.fixture(name="patch_get_vehicles")
def patch_get_vehicles(vehicle_type: str):
def patch_get_vehicles(vehicle_type: str) -> Generator[None]:
"""Mock fixtures."""
with patch(
"renault_api.renault_account.RenaultAccount.get_vehicles",
@ -123,149 +122,100 @@ def _get_fixtures(vehicle_type: str) -> MappingProxyType:
}
@contextlib.contextmanager
def patch_get_vehicle_data() -> Generator[dict[str, AsyncMock]]:
"""Mock get_vehicle_data methods."""
with (
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_battery_status"
) as get_battery_status,
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_charge_mode"
) as get_charge_mode,
patch("renault_api.renault_vehicle.RenaultVehicle.get_cockpit") as get_cockpit,
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_hvac_status"
) as get_hvac_status,
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_location"
) as get_location,
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_lock_status"
) as get_lock_status,
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_res_state"
) as get_res_state,
):
yield {
"battery_status": get_battery_status,
"charge_mode": get_charge_mode,
"cockpit": get_cockpit,
"hvac_status": get_hvac_status,
"location": get_location,
"lock_status": get_lock_status,
"res_state": get_res_state,
}
@pytest.fixture(name="fixtures_with_data")
def patch_fixtures_with_data(vehicle_type: str):
def patch_fixtures_with_data(vehicle_type: str) -> Generator[dict[str, AsyncMock]]:
"""Mock fixtures."""
mock_fixtures = _get_fixtures(vehicle_type)
with (
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_battery_status",
return_value=mock_fixtures["battery_status"],
),
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_charge_mode",
return_value=mock_fixtures["charge_mode"],
),
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_cockpit",
return_value=mock_fixtures["cockpit"],
),
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_hvac_status",
return_value=mock_fixtures["hvac_status"],
),
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_location",
return_value=mock_fixtures["location"],
),
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_lock_status",
return_value=mock_fixtures["lock_status"],
),
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_res_state",
return_value=mock_fixtures["res_state"],
),
):
yield
with patch_get_vehicle_data() as patches:
for key, value in patches.items():
value.return_value = mock_fixtures[key]
yield patches
@pytest.fixture(name="fixtures_with_no_data")
def patch_fixtures_with_no_data():
def patch_fixtures_with_no_data() -> Generator[dict[str, AsyncMock]]:
"""Mock fixtures."""
mock_fixtures = _get_fixtures("")
with (
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_battery_status",
return_value=mock_fixtures["battery_status"],
),
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_charge_mode",
return_value=mock_fixtures["charge_mode"],
),
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_cockpit",
return_value=mock_fixtures["cockpit"],
),
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_hvac_status",
return_value=mock_fixtures["hvac_status"],
),
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_location",
return_value=mock_fixtures["location"],
),
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_lock_status",
return_value=mock_fixtures["lock_status"],
),
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_res_state",
return_value=mock_fixtures["res_state"],
),
):
yield
@contextlib.contextmanager
def _patch_fixtures_with_side_effect(side_effect: Any) -> Iterator[None]:
"""Mock fixtures."""
with (
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_battery_status",
side_effect=side_effect,
),
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_charge_mode",
side_effect=side_effect,
),
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_cockpit",
side_effect=side_effect,
),
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_hvac_status",
side_effect=side_effect,
),
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_location",
side_effect=side_effect,
),
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_lock_status",
side_effect=side_effect,
),
patch(
"renault_api.renault_vehicle.RenaultVehicle.get_res_state",
side_effect=side_effect,
),
):
yield
with patch_get_vehicle_data() as patches:
for key, value in patches.items():
value.return_value = mock_fixtures[key]
yield patches
@pytest.fixture(name="fixtures_with_access_denied_exception")
def patch_fixtures_with_access_denied_exception():
def patch_fixtures_with_access_denied_exception() -> Generator[dict[str, AsyncMock]]:
"""Mock fixtures."""
access_denied_exception = exceptions.AccessDeniedException(
"err.func.403",
"Access is denied for this resource",
)
with _patch_fixtures_with_side_effect(access_denied_exception):
yield
with patch_get_vehicle_data() as patches:
for value in patches.values():
value.side_effect = access_denied_exception
yield patches
@pytest.fixture(name="fixtures_with_invalid_upstream_exception")
def patch_fixtures_with_invalid_upstream_exception():
def patch_fixtures_with_invalid_upstream_exception() -> Generator[dict[str, AsyncMock]]:
"""Mock fixtures."""
invalid_upstream_exception = exceptions.InvalidUpstreamException(
"err.tech.500",
"Invalid response from the upstream server (The request sent to the GDC is erroneous) ; 502 Bad Gateway",
)
with _patch_fixtures_with_side_effect(invalid_upstream_exception):
yield
with patch_get_vehicle_data() as patches:
for value in patches.values():
value.side_effect = invalid_upstream_exception
yield patches
@pytest.fixture(name="fixtures_with_not_supported_exception")
def patch_fixtures_with_not_supported_exception():
def patch_fixtures_with_not_supported_exception() -> Generator[dict[str, AsyncMock]]:
"""Mock fixtures."""
not_supported_exception = exceptions.NotSupportedException(
"err.tech.501",
"This feature is not technically supported by this gateway",
)
with _patch_fixtures_with_side_effect(not_supported_exception):
yield
with patch_get_vehicle_data() as patches:
for value in patches.values():
value.side_effect = not_supported_exception
yield patches

View File

@ -1,19 +1,25 @@
"""Tests for Renault sensors."""
from collections.abc import Generator
import datetime
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from renault_api.kamereon.exceptions import QuotaLimitException
from syrupy.assertion import SnapshotAssertion
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.const import STATE_UNAVAILABLE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from . import check_device_registry, check_entities_unavailable
from .conftest import _get_fixtures, patch_get_vehicle_data
from .const import MOCK_VEHICLES
from tests.common import async_fire_time_changed
pytestmark = pytest.mark.usefixtures("patch_renault_account", "patch_get_vehicles")
@ -150,3 +156,77 @@ async def test_sensor_not_supported(
check_device_registry(device_registry, mock_vehicle["expected_device"])
assert len(entity_registry.entities) == 0
@pytest.mark.parametrize("vehicle_type", ["zoe_40"], indirect=True)
async def test_sensor_throttling_during_setup(
hass: HomeAssistant,
config_entry: ConfigEntry,
vehicle_type: str,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test for Renault sensors with a throttling error during setup."""
mock_fixtures = _get_fixtures(vehicle_type)
with patch_get_vehicle_data() as patches:
for key, get_data_mock in patches.items():
get_data_mock.return_value = mock_fixtures[key]
get_data_mock.side_effect = QuotaLimitException(
"err.func.wired.overloaded", "You have reached your quota limit"
)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Initial state
entity_id = "sensor.reg_number_battery"
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
# Test QuotaLimitException recovery, with new battery level
for get_data_mock in patches.values():
get_data_mock.side_effect = None
patches["battery_status"].return_value.batteryLevel = 55
freezer.tick(datetime.timedelta(minutes=10))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == "55"
@pytest.mark.parametrize("vehicle_type", ["zoe_40"], indirect=True)
async def test_sensor_throttling_after_init(
hass: HomeAssistant,
config_entry: ConfigEntry,
vehicle_type: str,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test for Renault sensors with a throttling error during setup."""
mock_fixtures = _get_fixtures(vehicle_type)
with patch_get_vehicle_data() as patches:
for key, get_data_mock in patches.items():
get_data_mock.return_value = mock_fixtures[key]
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Initial state
entity_id = "sensor.reg_number_battery"
assert hass.states.get(entity_id).state == "60"
# Test QuotaLimitException state
for get_data_mock in patches.values():
get_data_mock.side_effect = QuotaLimitException(
"err.func.wired.overloaded", "You have reached your quota limit"
)
freezer.tick(datetime.timedelta(minutes=10))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
# Test QuotaLimitException recovery, with new battery level
for get_data_mock in patches.values():
get_data_mock.side_effect = None
patches["battery_status"].return_value.batteryLevel = 55
freezer.tick(datetime.timedelta(minutes=10))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == "55"