diff --git a/homeassistant/components/wallbox/__init__.py b/homeassistant/components/wallbox/__init__.py index 9336ab0e36b..c2983d540df 100644 --- a/homeassistant/components/wallbox/__init__.py +++ b/homeassistant/components/wallbox/__init__.py @@ -9,7 +9,7 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed -from .const import DOMAIN, UPDATE_INTERVAL +from .const import UPDATE_INTERVAL from .coordinator import InvalidAuth, WallboxCoordinator, async_validate_input PLATFORMS = [ @@ -20,8 +20,10 @@ PLATFORMS = [ Platform.SWITCH, ] +type WallboxConfigEntry = ConfigEntry[WallboxCoordinator] -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + +async def async_setup_entry(hass: HomeAssistant, entry: WallboxConfigEntry) -> bool: """Set up Wallbox from a config entry.""" wallbox = Wallbox( entry.data[CONF_USERNAME], @@ -36,7 +38,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: wallbox_coordinator = WallboxCoordinator(hass, entry, wallbox) await wallbox_coordinator.async_config_entry_first_refresh() - hass.data.setdefault(DOMAIN, {})[entry.entry_id] = wallbox_coordinator + entry.runtime_data = wallbox_coordinator await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) @@ -45,8 +47,4 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" - unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - if unload_ok: - hass.data[DOMAIN].pop(entry.entry_id) - - return unload_ok + return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/wallbox/coordinator.py b/homeassistant/components/wallbox/coordinator.py index 69bf3a3af1c..ffd235157ac 100644 --- a/homeassistant/components/wallbox/coordinator.py +++ b/homeassistant/components/wallbox/coordinator.py @@ -222,7 +222,9 @@ class WallboxCoordinator(DataUpdateCoordinator[dict[str, Any]]): return data # noqa: TRY300 except requests.exceptions.HTTPError as wallbox_connection_error: if wallbox_connection_error.response.status_code == 403: - raise InvalidAuth from wallbox_connection_error + raise InvalidAuth( + translation_domain=DOMAIN, translation_key="invalid_auth" + ) from wallbox_connection_error if wallbox_connection_error.response.status_code == 429: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="too_many_requests" @@ -248,7 +250,9 @@ class WallboxCoordinator(DataUpdateCoordinator[dict[str, Any]]): return data # noqa: TRY300 except requests.exceptions.HTTPError as wallbox_connection_error: if wallbox_connection_error.response.status_code == 403: - raise InvalidAuth from wallbox_connection_error + raise InvalidAuth( + translation_domain=DOMAIN, translation_key="invalid_auth" + ) from wallbox_connection_error if wallbox_connection_error.response.status_code == 429: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="too_many_requests" @@ -303,7 +307,9 @@ class WallboxCoordinator(DataUpdateCoordinator[dict[str, Any]]): return data # noqa: TRY300 except requests.exceptions.HTTPError as wallbox_connection_error: if wallbox_connection_error.response.status_code == 403: - raise InvalidAuth from wallbox_connection_error + raise InvalidAuth( + translation_domain=DOMAIN, translation_key="invalid_auth" + ) from wallbox_connection_error if wallbox_connection_error.response.status_code == 429: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="too_many_requests" diff --git a/homeassistant/components/wallbox/lock.py b/homeassistant/components/wallbox/lock.py index 7b5c99340f8..6ba9058db96 100644 --- a/homeassistant/components/wallbox/lock.py +++ b/homeassistant/components/wallbox/lock.py @@ -13,7 +13,6 @@ from .const import ( CHARGER_DATA_KEY, CHARGER_LOCKED_UNLOCKED_KEY, CHARGER_SERIAL_NUMBER_KEY, - DOMAIN, ) from .coordinator import WallboxCoordinator from .entity import WallboxEntity @@ -32,7 +31,7 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Create wallbox lock entities in HASS.""" - coordinator: WallboxCoordinator = hass.data[DOMAIN][entry.entry_id] + coordinator: WallboxCoordinator = entry.runtime_data async_add_entities( WallboxLock(coordinator, description) for ent in coordinator.data @@ -40,6 +39,10 @@ async def async_setup_entry( ) +# Coordinator is used to centralize the data updates +PARALLEL_UPDATES = 0 + + class WallboxLock(WallboxEntity, LockEntity): """Representation of a wallbox lock.""" diff --git a/homeassistant/components/wallbox/number.py b/homeassistant/components/wallbox/number.py index e1b044bbdb2..af4fbe2c38b 100644 --- a/homeassistant/components/wallbox/number.py +++ b/homeassistant/components/wallbox/number.py @@ -23,7 +23,6 @@ from .const import ( CHARGER_MAX_ICP_CURRENT_KEY, CHARGER_PART_NUMBER_KEY, CHARGER_SERIAL_NUMBER_KEY, - DOMAIN, ) from .coordinator import WallboxCoordinator from .entity import WallboxEntity @@ -84,7 +83,7 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Create wallbox number entities in HASS.""" - coordinator: WallboxCoordinator = hass.data[DOMAIN][entry.entry_id] + coordinator: WallboxCoordinator = entry.runtime_data async_add_entities( WallboxNumber(coordinator, entry, description) for ent in coordinator.data @@ -92,6 +91,10 @@ async def async_setup_entry( ) +# Coordinator is used to centralize the data updates +PARALLEL_UPDATES = 0 + + class WallboxNumber(WallboxEntity, NumberEntity): """Representation of the Wallbox portal.""" diff --git a/homeassistant/components/wallbox/select.py b/homeassistant/components/wallbox/select.py index 0048aa35c7c..10ac4e61189 100644 --- a/homeassistant/components/wallbox/select.py +++ b/homeassistant/components/wallbox/select.py @@ -62,7 +62,7 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Create wallbox select entities in HASS.""" - coordinator: WallboxCoordinator = hass.data[DOMAIN][entry.entry_id] + coordinator: WallboxCoordinator = entry.runtime_data if coordinator.data[CHARGER_ECO_SMART_KEY] != EcoSmartMode.DISABLED: async_add_entities( WallboxSelect(coordinator, description) @@ -74,6 +74,10 @@ async def async_setup_entry( ) +# Coordinator is used to centralize the data updates +PARALLEL_UPDATES = 0 + + class WallboxSelect(WallboxEntity, SelectEntity): """Representation of the Wallbox portal.""" diff --git a/homeassistant/components/wallbox/sensor.py b/homeassistant/components/wallbox/sensor.py index e19fc2b936a..7d5e5b56309 100644 --- a/homeassistant/components/wallbox/sensor.py +++ b/homeassistant/components/wallbox/sensor.py @@ -43,7 +43,6 @@ from .const import ( CHARGER_SERIAL_NUMBER_KEY, CHARGER_STATE_OF_CHARGE_KEY, CHARGER_STATUS_DESCRIPTION_KEY, - DOMAIN, ) from .coordinator import WallboxCoordinator from .entity import WallboxEntity @@ -174,7 +173,7 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Create wallbox sensor entities in HASS.""" - coordinator: WallboxCoordinator = hass.data[DOMAIN][entry.entry_id] + coordinator: WallboxCoordinator = entry.runtime_data async_add_entities( WallboxSensor(coordinator, description) @@ -183,6 +182,10 @@ async def async_setup_entry( ) +# Coordinator is used to centralize the data updates +PARALLEL_UPDATES = 0 + + class WallboxSensor(WallboxEntity, SensorEntity): """Representation of the Wallbox portal.""" diff --git a/homeassistant/components/wallbox/strings.json b/homeassistant/components/wallbox/strings.json index ee98a4855e3..a69251eb832 100644 --- a/homeassistant/components/wallbox/strings.json +++ b/homeassistant/components/wallbox/strings.json @@ -6,6 +6,11 @@ "station": "Station Serial Number", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]" + }, + "data_description": { + "station": "Serial number of the charger, this value can be found in the Wallbox App or in the Wallbox Portal.", + "username": "Username for your Wallbox Account.", + "password": "Password for your Wallbox Account." } }, "reauth_confirm": { @@ -115,6 +120,9 @@ }, "too_many_requests": { "message": "Error communicating with Wallbox API, too many requests" + }, + "invalid_auth": { + "message": "Invalid authentication" } } } diff --git a/homeassistant/components/wallbox/switch.py b/homeassistant/components/wallbox/switch.py index 30275951ab2..7a28f863c4d 100644 --- a/homeassistant/components/wallbox/switch.py +++ b/homeassistant/components/wallbox/switch.py @@ -14,7 +14,6 @@ from .const import ( CHARGER_PAUSE_RESUME_KEY, CHARGER_SERIAL_NUMBER_KEY, CHARGER_STATUS_DESCRIPTION_KEY, - DOMAIN, ChargerStatus, ) from .coordinator import WallboxCoordinator @@ -34,12 +33,16 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Create wallbox sensor entities in HASS.""" - coordinator: WallboxCoordinator = hass.data[DOMAIN][entry.entry_id] + coordinator: WallboxCoordinator = entry.runtime_data async_add_entities( [WallboxSwitch(coordinator, SWITCH_TYPES[CHARGER_PAUSE_RESUME_KEY])] ) +# Coordinator is used to centralize the data updates +PARALLEL_UPDATES = 0 + + class WallboxSwitch(WallboxEntity, SwitchEntity): """Representation of the Wallbox portal.""" diff --git a/tests/components/wallbox/conftest.py b/tests/components/wallbox/conftest.py index ab1032b3816..c20c6e59da1 100644 --- a/tests/components/wallbox/conftest.py +++ b/tests/components/wallbox/conftest.py @@ -7,165 +7,22 @@ import pytest import requests from homeassistant.components.wallbox.const import ( - CHARGER_ADDED_ENERGY_KEY, - CHARGER_ADDED_RANGE_KEY, - CHARGER_CHARGING_POWER_KEY, - CHARGER_CHARGING_SPEED_KEY, - CHARGER_CURRENCY_KEY, - CHARGER_CURRENT_VERSION_KEY, - CHARGER_DATA_KEY, CHARGER_DATA_POST_L1_KEY, CHARGER_DATA_POST_L2_KEY, - CHARGER_ECO_SMART_KEY, - CHARGER_ECO_SMART_MODE_KEY, - CHARGER_ECO_SMART_STATUS_KEY, CHARGER_ENERGY_PRICE_KEY, - CHARGER_FEATURES_KEY, CHARGER_LOCKED_UNLOCKED_KEY, - CHARGER_MAX_AVAILABLE_POWER_KEY, - CHARGER_MAX_CHARGING_CURRENT_KEY, CHARGER_MAX_CHARGING_CURRENT_POST_KEY, CHARGER_MAX_ICP_CURRENT_KEY, - CHARGER_NAME_KEY, - CHARGER_PART_NUMBER_KEY, - CHARGER_PLAN_KEY, - CHARGER_POWER_BOOST_KEY, - CHARGER_SERIAL_NUMBER_KEY, - CHARGER_SOFTWARE_KEY, - CHARGER_STATUS_ID_KEY, CONF_STATION, DOMAIN, ) from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant -from .const import ERROR, REFRESH_TOKEN_TTL, STATUS, TTL, USER_ID +from .const import WALLBOX_AUTHORISATION_RESPONSE, WALLBOX_STATUS_RESPONSE from tests.common import MockConfigEntry -test_response = { - CHARGER_CHARGING_POWER_KEY: 0, - CHARGER_STATUS_ID_KEY: 193, - CHARGER_MAX_AVAILABLE_POWER_KEY: 25.0, - CHARGER_CHARGING_SPEED_KEY: 0, - CHARGER_ADDED_RANGE_KEY: 150, - CHARGER_ADDED_ENERGY_KEY: 44.697, - CHARGER_NAME_KEY: "WallboxName", - CHARGER_DATA_KEY: { - CHARGER_MAX_CHARGING_CURRENT_KEY: 24, - CHARGER_ENERGY_PRICE_KEY: 0.4, - CHARGER_LOCKED_UNLOCKED_KEY: False, - CHARGER_SERIAL_NUMBER_KEY: "20000", - CHARGER_PART_NUMBER_KEY: "PLP1-0-2-4-9-002-E", - CHARGER_SOFTWARE_KEY: {CHARGER_CURRENT_VERSION_KEY: "5.5.10"}, - CHARGER_CURRENCY_KEY: {"code": "EUR/kWh"}, - CHARGER_MAX_ICP_CURRENT_KEY: 20, - CHARGER_PLAN_KEY: {CHARGER_FEATURES_KEY: [CHARGER_POWER_BOOST_KEY]}, - CHARGER_ECO_SMART_KEY: { - CHARGER_ECO_SMART_STATUS_KEY: False, - CHARGER_ECO_SMART_MODE_KEY: 0, - }, - }, -} - -test_response_bidir = { - CHARGER_CHARGING_POWER_KEY: 0, - CHARGER_STATUS_ID_KEY: 193, - CHARGER_MAX_AVAILABLE_POWER_KEY: 25.0, - CHARGER_CHARGING_SPEED_KEY: 0, - CHARGER_ADDED_RANGE_KEY: 150, - CHARGER_ADDED_ENERGY_KEY: 44.697, - CHARGER_NAME_KEY: "WallboxName", - CHARGER_DATA_KEY: { - CHARGER_MAX_CHARGING_CURRENT_KEY: 24, - CHARGER_ENERGY_PRICE_KEY: 0.4, - CHARGER_LOCKED_UNLOCKED_KEY: False, - CHARGER_SERIAL_NUMBER_KEY: "20000", - CHARGER_PART_NUMBER_KEY: "QSP1-0-2-4-9-002-E", - CHARGER_SOFTWARE_KEY: {CHARGER_CURRENT_VERSION_KEY: "5.5.10"}, - CHARGER_CURRENCY_KEY: {"code": "EUR/kWh"}, - CHARGER_MAX_ICP_CURRENT_KEY: 20, - CHARGER_PLAN_KEY: {CHARGER_FEATURES_KEY: [CHARGER_POWER_BOOST_KEY]}, - CHARGER_ECO_SMART_KEY: { - CHARGER_ECO_SMART_STATUS_KEY: False, - CHARGER_ECO_SMART_MODE_KEY: 0, - }, - }, -} - -test_response_eco_mode = { - CHARGER_CHARGING_POWER_KEY: 0, - CHARGER_STATUS_ID_KEY: 193, - CHARGER_MAX_AVAILABLE_POWER_KEY: 25.0, - CHARGER_CHARGING_SPEED_KEY: 0, - CHARGER_ADDED_RANGE_KEY: 150, - CHARGER_ADDED_ENERGY_KEY: 44.697, - CHARGER_NAME_KEY: "WallboxName", - CHARGER_DATA_KEY: { - CHARGER_MAX_CHARGING_CURRENT_KEY: 24, - CHARGER_ENERGY_PRICE_KEY: 0.4, - CHARGER_LOCKED_UNLOCKED_KEY: False, - CHARGER_SERIAL_NUMBER_KEY: "20000", - CHARGER_PART_NUMBER_KEY: "PLP1-0-2-4-9-002-E", - CHARGER_SOFTWARE_KEY: {CHARGER_CURRENT_VERSION_KEY: "5.5.10"}, - CHARGER_CURRENCY_KEY: {"code": "EUR/kWh"}, - CHARGER_MAX_ICP_CURRENT_KEY: 20, - CHARGER_PLAN_KEY: {CHARGER_FEATURES_KEY: [CHARGER_POWER_BOOST_KEY]}, - CHARGER_ECO_SMART_KEY: { - CHARGER_ECO_SMART_STATUS_KEY: True, - CHARGER_ECO_SMART_MODE_KEY: 0, - }, - }, -} - - -test_response_full_solar = { - CHARGER_CHARGING_POWER_KEY: 0, - CHARGER_STATUS_ID_KEY: 193, - CHARGER_MAX_AVAILABLE_POWER_KEY: 25.0, - CHARGER_CHARGING_SPEED_KEY: 0, - CHARGER_ADDED_RANGE_KEY: 150, - CHARGER_ADDED_ENERGY_KEY: 44.697, - CHARGER_NAME_KEY: "WallboxName", - CHARGER_DATA_KEY: { - CHARGER_MAX_CHARGING_CURRENT_KEY: 24, - CHARGER_ENERGY_PRICE_KEY: 0.4, - CHARGER_LOCKED_UNLOCKED_KEY: False, - CHARGER_SERIAL_NUMBER_KEY: "20000", - CHARGER_PART_NUMBER_KEY: "PLP1-0-2-4-9-002-E", - CHARGER_SOFTWARE_KEY: {CHARGER_CURRENT_VERSION_KEY: "5.5.10"}, - CHARGER_CURRENCY_KEY: {"code": "EUR/kWh"}, - CHARGER_MAX_ICP_CURRENT_KEY: 20, - CHARGER_PLAN_KEY: {CHARGER_FEATURES_KEY: [CHARGER_POWER_BOOST_KEY]}, - CHARGER_ECO_SMART_KEY: { - CHARGER_ECO_SMART_STATUS_KEY: True, - CHARGER_ECO_SMART_MODE_KEY: 1, - }, - }, -} - -test_response_no_power_boost = { - CHARGER_CHARGING_POWER_KEY: 0, - CHARGER_STATUS_ID_KEY: 193, - CHARGER_MAX_AVAILABLE_POWER_KEY: 25.0, - CHARGER_CHARGING_SPEED_KEY: 0, - CHARGER_ADDED_RANGE_KEY: 150, - CHARGER_ADDED_ENERGY_KEY: 44.697, - CHARGER_NAME_KEY: "WallboxName", - CHARGER_DATA_KEY: { - CHARGER_MAX_CHARGING_CURRENT_KEY: 24, - CHARGER_ENERGY_PRICE_KEY: 0.4, - CHARGER_LOCKED_UNLOCKED_KEY: False, - CHARGER_SERIAL_NUMBER_KEY: "20000", - CHARGER_PART_NUMBER_KEY: "PLP1-0-2-4-9-002-E", - CHARGER_SOFTWARE_KEY: {CHARGER_CURRENT_VERSION_KEY: "5.5.10"}, - CHARGER_CURRENCY_KEY: {"code": "EUR/kWh"}, - CHARGER_MAX_ICP_CURRENT_KEY: 20, - CHARGER_PLAN_KEY: {CHARGER_FEATURES_KEY: []}, - }, -} - - http_403_error = requests.exceptions.HTTPError() http_403_error.response = requests.Response() http_403_error.response.status_code = HTTPStatus.FORBIDDEN @@ -176,45 +33,6 @@ http_429_error = requests.exceptions.HTTPError() http_429_error.response = requests.Response() http_429_error.response.status_code = HTTPStatus.TOO_MANY_REQUESTS -authorisation_response = { - "data": { - "attributes": { - "token": "fakekeyhere", - "refresh_token": "refresh_fakekeyhere", - USER_ID: 12345, - TTL: 145656758, - REFRESH_TOKEN_TTL: 145756758, - ERROR: "false", - STATUS: 200, - } - } -} - - -authorisation_response_unauthorised = { - "data": { - "attributes": { - "token": "fakekeyhere", - "refresh_token": "refresh_fakekeyhere", - USER_ID: 12345, - TTL: 145656758, - REFRESH_TOKEN_TTL: 145756758, - ERROR: "false", - STATUS: 404, - } - } -} - -invalid_reauth_response = { - "jwt": "fakekeyhere", - "refresh_token": "refresh_fakekeyhere", - "user_id": 12345, - "ttl": 145656758, - "refresh_token_ttl": 145756758, - "error": False, - "status": 200, -} - @pytest.fixture def entry(hass: HomeAssistant) -> MockConfigEntry: @@ -237,7 +55,7 @@ def mock_wallbox(): """Patch Wallbox class for tests.""" with patch("homeassistant.components.wallbox.Wallbox") as mock: wallbox = MagicMock() - wallbox.authenticate = Mock(return_value=authorisation_response) + wallbox.authenticate = Mock(return_value=WALLBOX_AUTHORISATION_RESPONSE) wallbox.lockCharger = Mock( return_value={ CHARGER_DATA_POST_L1_KEY: { @@ -263,7 +81,7 @@ def mock_wallbox(): } ) wallbox.setIcpMaxCurrent = Mock(return_value={CHARGER_MAX_ICP_CURRENT_KEY: 25}) - wallbox.getChargerStatus = Mock(return_value=test_response) + wallbox.getChargerStatus = Mock(return_value=WALLBOX_STATUS_RESPONSE) mock.return_value = wallbox yield wallbox diff --git a/tests/components/wallbox/const.py b/tests/components/wallbox/const.py index 82c9e5169d5..9650f9d3c61 100644 --- a/tests/components/wallbox/const.py +++ b/tests/components/wallbox/const.py @@ -1,5 +1,31 @@ """Provides constants for Wallbox component tests.""" +from homeassistant.components.wallbox.const import ( + CHARGER_ADDED_ENERGY_KEY, + CHARGER_ADDED_RANGE_KEY, + CHARGER_CHARGING_POWER_KEY, + CHARGER_CHARGING_SPEED_KEY, + CHARGER_CURRENCY_KEY, + CHARGER_CURRENT_VERSION_KEY, + CHARGER_DATA_KEY, + CHARGER_ECO_SMART_KEY, + CHARGER_ECO_SMART_MODE_KEY, + CHARGER_ECO_SMART_STATUS_KEY, + CHARGER_ENERGY_PRICE_KEY, + CHARGER_FEATURES_KEY, + CHARGER_LOCKED_UNLOCKED_KEY, + CHARGER_MAX_AVAILABLE_POWER_KEY, + CHARGER_MAX_CHARGING_CURRENT_KEY, + CHARGER_MAX_ICP_CURRENT_KEY, + CHARGER_NAME_KEY, + CHARGER_PART_NUMBER_KEY, + CHARGER_PLAN_KEY, + CHARGER_POWER_BOOST_KEY, + CHARGER_SERIAL_NUMBER_KEY, + CHARGER_SOFTWARE_KEY, + CHARGER_STATUS_ID_KEY, +) + JWT = "jwt" USER_ID = "user_id" TTL = "ttl" @@ -7,6 +33,169 @@ REFRESH_TOKEN_TTL = "refresh_token_ttl" ERROR = "error" STATUS = "status" +WALLBOX_STATUS_RESPONSE = { + CHARGER_CHARGING_POWER_KEY: 0, + CHARGER_STATUS_ID_KEY: 193, + CHARGER_MAX_AVAILABLE_POWER_KEY: 25.0, + CHARGER_CHARGING_SPEED_KEY: 0, + CHARGER_ADDED_RANGE_KEY: 150, + CHARGER_ADDED_ENERGY_KEY: 44.697, + CHARGER_NAME_KEY: "WallboxName", + CHARGER_DATA_KEY: { + CHARGER_MAX_CHARGING_CURRENT_KEY: 24, + CHARGER_ENERGY_PRICE_KEY: 0.4, + CHARGER_LOCKED_UNLOCKED_KEY: False, + CHARGER_SERIAL_NUMBER_KEY: "20000", + CHARGER_PART_NUMBER_KEY: "PLP1-0-2-4-9-002-E", + CHARGER_SOFTWARE_KEY: {CHARGER_CURRENT_VERSION_KEY: "5.5.10"}, + CHARGER_CURRENCY_KEY: {"code": "EUR/kWh"}, + CHARGER_MAX_ICP_CURRENT_KEY: 20, + CHARGER_PLAN_KEY: {CHARGER_FEATURES_KEY: [CHARGER_POWER_BOOST_KEY]}, + CHARGER_ECO_SMART_KEY: { + CHARGER_ECO_SMART_STATUS_KEY: False, + CHARGER_ECO_SMART_MODE_KEY: 0, + }, + }, +} + +WALLBOX_STATUS_RESPONSE_BIDIR = { + CHARGER_CHARGING_POWER_KEY: 0, + CHARGER_STATUS_ID_KEY: 193, + CHARGER_MAX_AVAILABLE_POWER_KEY: 25.0, + CHARGER_CHARGING_SPEED_KEY: 0, + CHARGER_ADDED_RANGE_KEY: 150, + CHARGER_ADDED_ENERGY_KEY: 44.697, + CHARGER_NAME_KEY: "WallboxName", + CHARGER_DATA_KEY: { + CHARGER_MAX_CHARGING_CURRENT_KEY: 24, + CHARGER_ENERGY_PRICE_KEY: 0.4, + CHARGER_LOCKED_UNLOCKED_KEY: False, + CHARGER_SERIAL_NUMBER_KEY: "20000", + CHARGER_PART_NUMBER_KEY: "QSP1-0-2-4-9-002-E", + CHARGER_SOFTWARE_KEY: {CHARGER_CURRENT_VERSION_KEY: "5.5.10"}, + CHARGER_CURRENCY_KEY: {"code": "EUR/kWh"}, + CHARGER_MAX_ICP_CURRENT_KEY: 20, + CHARGER_PLAN_KEY: {CHARGER_FEATURES_KEY: [CHARGER_POWER_BOOST_KEY]}, + CHARGER_ECO_SMART_KEY: { + CHARGER_ECO_SMART_STATUS_KEY: False, + CHARGER_ECO_SMART_MODE_KEY: 0, + }, + }, +} + +WALLBOX_STATUS_RESPONSE_ECO_MODE = { + CHARGER_CHARGING_POWER_KEY: 0, + CHARGER_STATUS_ID_KEY: 193, + CHARGER_MAX_AVAILABLE_POWER_KEY: 25.0, + CHARGER_CHARGING_SPEED_KEY: 0, + CHARGER_ADDED_RANGE_KEY: 150, + CHARGER_ADDED_ENERGY_KEY: 44.697, + CHARGER_NAME_KEY: "WallboxName", + CHARGER_DATA_KEY: { + CHARGER_MAX_CHARGING_CURRENT_KEY: 24, + CHARGER_ENERGY_PRICE_KEY: 0.4, + CHARGER_LOCKED_UNLOCKED_KEY: False, + CHARGER_SERIAL_NUMBER_KEY: "20000", + CHARGER_PART_NUMBER_KEY: "PLP1-0-2-4-9-002-E", + CHARGER_SOFTWARE_KEY: {CHARGER_CURRENT_VERSION_KEY: "5.5.10"}, + CHARGER_CURRENCY_KEY: {"code": "EUR/kWh"}, + CHARGER_MAX_ICP_CURRENT_KEY: 20, + CHARGER_PLAN_KEY: {CHARGER_FEATURES_KEY: [CHARGER_POWER_BOOST_KEY]}, + CHARGER_ECO_SMART_KEY: { + CHARGER_ECO_SMART_STATUS_KEY: True, + CHARGER_ECO_SMART_MODE_KEY: 0, + }, + }, +} + + +WALLBOX_STATUS_RESPONSE_FULL_SOLAR = { + CHARGER_CHARGING_POWER_KEY: 0, + CHARGER_STATUS_ID_KEY: 193, + CHARGER_MAX_AVAILABLE_POWER_KEY: 25.0, + CHARGER_CHARGING_SPEED_KEY: 0, + CHARGER_ADDED_RANGE_KEY: 150, + CHARGER_ADDED_ENERGY_KEY: 44.697, + CHARGER_NAME_KEY: "WallboxName", + CHARGER_DATA_KEY: { + CHARGER_MAX_CHARGING_CURRENT_KEY: 24, + CHARGER_ENERGY_PRICE_KEY: 0.4, + CHARGER_LOCKED_UNLOCKED_KEY: False, + CHARGER_SERIAL_NUMBER_KEY: "20000", + CHARGER_PART_NUMBER_KEY: "PLP1-0-2-4-9-002-E", + CHARGER_SOFTWARE_KEY: {CHARGER_CURRENT_VERSION_KEY: "5.5.10"}, + CHARGER_CURRENCY_KEY: {"code": "EUR/kWh"}, + CHARGER_MAX_ICP_CURRENT_KEY: 20, + CHARGER_PLAN_KEY: {CHARGER_FEATURES_KEY: [CHARGER_POWER_BOOST_KEY]}, + CHARGER_ECO_SMART_KEY: { + CHARGER_ECO_SMART_STATUS_KEY: True, + CHARGER_ECO_SMART_MODE_KEY: 1, + }, + }, +} + +WALLBOX_STATUS_RESPONSE_NO_POWER_BOOST = { + CHARGER_CHARGING_POWER_KEY: 0, + CHARGER_STATUS_ID_KEY: 193, + CHARGER_MAX_AVAILABLE_POWER_KEY: 25.0, + CHARGER_CHARGING_SPEED_KEY: 0, + CHARGER_ADDED_RANGE_KEY: 150, + CHARGER_ADDED_ENERGY_KEY: 44.697, + CHARGER_NAME_KEY: "WallboxName", + CHARGER_DATA_KEY: { + CHARGER_MAX_CHARGING_CURRENT_KEY: 24, + CHARGER_ENERGY_PRICE_KEY: 0.4, + CHARGER_LOCKED_UNLOCKED_KEY: False, + CHARGER_SERIAL_NUMBER_KEY: "20000", + CHARGER_PART_NUMBER_KEY: "PLP1-0-2-4-9-002-E", + CHARGER_SOFTWARE_KEY: {CHARGER_CURRENT_VERSION_KEY: "5.5.10"}, + CHARGER_CURRENCY_KEY: {"code": "EUR/kWh"}, + CHARGER_MAX_ICP_CURRENT_KEY: 20, + CHARGER_PLAN_KEY: {CHARGER_FEATURES_KEY: []}, + }, +} + + +WALLBOX_AUTHORISATION_RESPONSE = { + "data": { + "attributes": { + "token": "fakekeyhere", + "refresh_token": "refresh_fakekeyhere", + USER_ID: 12345, + TTL: 145656758, + REFRESH_TOKEN_TTL: 145756758, + ERROR: "false", + STATUS: 200, + } + } +} + + +WALLBOX_AUTHORISATION_RESPONSE_UNAUTHORISED = { + "data": { + "attributes": { + "token": "fakekeyhere", + "refresh_token": "refresh_fakekeyhere", + USER_ID: 12345, + TTL: 145656758, + REFRESH_TOKEN_TTL: 145756758, + ERROR: "false", + STATUS: 404, + } + } +} + +WALLBOX_INVALID_REAUTH_RESPONSE = { + "jwt": "fakekeyhere", + "refresh_token": "refresh_fakekeyhere", + "user_id": 12345, + "ttl": 145656758, + "refresh_token_ttl": 145756758, + "error": False, + "status": 200, +} + + MOCK_NUMBER_ENTITY_ID = "number.wallbox_wallboxname_maximum_charging_current" MOCK_NUMBER_ENTITY_ENERGY_PRICE_ID = "number.wallbox_wallboxname_energy_price" MOCK_NUMBER_ENTITY_ICP_CURRENT_ID = "number.wallbox_wallboxname_maximum_icp_current" diff --git a/tests/components/wallbox/test_config_flow.py b/tests/components/wallbox/test_config_flow.py index d0c34ae0bce..25265aeda4a 100644 --- a/tests/components/wallbox/test_config_flow.py +++ b/tests/components/wallbox/test_config_flow.py @@ -3,7 +3,6 @@ from unittest.mock import Mock, patch from homeassistant import config_entries -from homeassistant.components.wallbox import config_flow from homeassistant.components.wallbox.const import ( CHARGER_ADDED_ENERGY_KEY, CHARGER_ADDED_RANGE_KEY, @@ -18,12 +17,10 @@ from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType -from .conftest import ( - authorisation_response, - authorisation_response_unauthorised, - http_403_error, - http_404_error, - setup_integration, +from .conftest import http_403_error, http_404_error, setup_integration +from .const import ( + WALLBOX_AUTHORISATION_RESPONSE, + WALLBOX_AUTHORISATION_RESPONSE_UNAUTHORISED, ) from tests.common import MockConfigEntry @@ -40,10 +37,9 @@ test_response = { async def test_show_set_form(hass: HomeAssistant, mock_wallbox) -> None: """Test that the setup form is served.""" - flow = config_flow.WallboxConfigFlow() - flow.hass = hass - result = await flow.async_step_user(user_input=None) - + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) assert result["type"] is FlowResultType.FORM assert result["step_id"] == "user" @@ -112,7 +108,7 @@ async def test_form_validate_input(hass: HomeAssistant) -> None: with ( patch( "homeassistant.components.wallbox.Wallbox.authenticate", - return_value=authorisation_response, + return_value=WALLBOX_AUTHORISATION_RESPONSE, ), patch( "homeassistant.components.wallbox.Wallbox.pauseChargingSession", @@ -143,7 +139,7 @@ async def test_form_reauth( patch.object( mock_wallbox, "authenticate", - return_value=authorisation_response_unauthorised, + return_value=WALLBOX_AUTHORISATION_RESPONSE_UNAUTHORISED, ), patch.object(mock_wallbox, "getChargerStatus", return_value=test_response), ): @@ -176,7 +172,7 @@ async def test_form_reauth_invalid( patch.object( mock_wallbox, "authenticate", - return_value=authorisation_response_unauthorised, + return_value=WALLBOX_AUTHORISATION_RESPONSE_UNAUTHORISED, ), patch.object(mock_wallbox, "getChargerStatus", return_value=test_response), ): diff --git a/tests/components/wallbox/test_init.py b/tests/components/wallbox/test_init.py index ef73decea8f..4d882da7a6e 100644 --- a/tests/components/wallbox/test_init.py +++ b/tests/components/wallbox/test_init.py @@ -1,19 +1,23 @@ """Test Wallbox Init Component.""" +from datetime import datetime, timedelta from unittest.mock import patch -from homeassistant.components.wallbox.const import DOMAIN -from homeassistant.config_entries import ConfigEntryState -from homeassistant.core import HomeAssistant +import pytest -from .conftest import ( - http_403_error, - http_429_error, - setup_integration, - test_response_no_power_boost, +from homeassistant.components.input_number import ATTR_VALUE, SERVICE_SET_VALUE +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError + +from .conftest import http_403_error, http_429_error, setup_integration +from .const import ( + MOCK_NUMBER_ENTITY_ENERGY_PRICE_ID, + WALLBOX_STATUS_RESPONSE_NO_POWER_BOOST, ) -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_fire_time_changed async def test_wallbox_setup_unload_entry( @@ -40,24 +44,25 @@ async def test_wallbox_unload_entry_connection_error( assert entry.state is ConfigEntryState.NOT_LOADED -async def test_wallbox_refresh_failed_connection_error_auth( +async def test_wallbox_refresh_failed_connection_error_too_many_requests( hass: HomeAssistant, entry: MockConfigEntry, mock_wallbox ) -> None: """Test Wallbox setup with connection error.""" - await setup_integration(hass, entry) - assert entry.state is ConfigEntryState.LOADED + with patch.object(mock_wallbox, "getChargerStatus", side_effect=http_429_error): + await setup_integration(hass, entry) + assert entry.state is ConfigEntryState.SETUP_RETRY - with patch.object(mock_wallbox, "authenticate", side_effect=http_429_error): - wallbox = hass.data[DOMAIN][entry.entry_id] - await wallbox.async_refresh() + await hass.async_block_till_done() assert await hass.config_entries.async_unload(entry.entry_id) assert entry.state is ConfigEntryState.NOT_LOADED -async def test_wallbox_refresh_failed_invalid_auth( - hass: HomeAssistant, entry: MockConfigEntry, mock_wallbox +async def test_wallbox_refresh_failed_error_auth( + hass: HomeAssistant, + entry: MockConfigEntry, + mock_wallbox, ) -> None: """Test Wallbox setup with authentication error.""" @@ -66,11 +71,31 @@ async def test_wallbox_refresh_failed_invalid_auth( with ( patch.object(mock_wallbox, "authenticate", side_effect=http_403_error), - patch.object(mock_wallbox, "pauseChargingSession", side_effect=http_403_error), + pytest.raises(HomeAssistantError), ): - wallbox = hass.data[DOMAIN][entry.entry_id] + await hass.services.async_call( + "number", + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: MOCK_NUMBER_ENTITY_ENERGY_PRICE_ID, + ATTR_VALUE: 1.1, + }, + blocking=True, + ) - await wallbox.async_refresh() + with ( + patch.object(mock_wallbox, "authenticate", side_effect=http_429_error), + pytest.raises(HomeAssistantError), + ): + await hass.services.async_call( + "number", + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: MOCK_NUMBER_ENTITY_ENERGY_PRICE_ID, + ATTR_VALUE: 1.1, + }, + blocking=True, + ) assert await hass.config_entries.async_unload(entry.entry_id) assert entry.state is ConfigEntryState.NOT_LOADED @@ -81,13 +106,10 @@ async def test_wallbox_refresh_failed_http_error( ) -> None: """Test Wallbox setup with authentication error.""" - await setup_integration(hass, entry) - assert entry.state is ConfigEntryState.LOADED - with patch.object(mock_wallbox, "getChargerStatus", side_effect=http_403_error): - wallbox = hass.data[DOMAIN][entry.entry_id] - - await wallbox.async_refresh() + await setup_integration(hass, entry) + assert entry.state is ConfigEntryState.SETUP_RETRY + await hass.async_block_till_done() assert await hass.config_entries.async_unload(entry.entry_id) assert entry.state is ConfigEntryState.NOT_LOADED @@ -102,9 +124,8 @@ async def test_wallbox_refresh_failed_too_many_requests( assert entry.state is ConfigEntryState.LOADED with patch.object(mock_wallbox, "getChargerStatus", side_effect=http_429_error): - wallbox = hass.data[DOMAIN][entry.entry_id] - - await wallbox.async_refresh() + async_fire_time_changed(hass, datetime.now() + timedelta(seconds=120), True) + await hass.async_block_till_done() assert await hass.config_entries.async_unload(entry.entry_id) assert entry.state is ConfigEntryState.NOT_LOADED @@ -119,9 +140,8 @@ async def test_wallbox_refresh_failed_connection_error( assert entry.state is ConfigEntryState.LOADED with patch.object(mock_wallbox, "pauseChargingSession", side_effect=http_403_error): - wallbox = hass.data[DOMAIN][entry.entry_id] - - await wallbox.async_refresh() + async_fire_time_changed(hass, datetime.now() + timedelta(seconds=120), True) + await hass.async_block_till_done() assert await hass.config_entries.async_unload(entry.entry_id) assert entry.state is ConfigEntryState.NOT_LOADED @@ -132,7 +152,9 @@ async def test_wallbox_setup_load_entry_no_eco_mode( ) -> None: """Test Wallbox Unload.""" with patch.object( - mock_wallbox, "getChargerStatus", return_value=test_response_no_power_boost + mock_wallbox, + "getChargerStatus", + return_value=WALLBOX_STATUS_RESPONSE_NO_POWER_BOOST, ): await setup_integration(hass, entry) assert entry.state is ConfigEntryState.LOADED diff --git a/tests/components/wallbox/test_number.py b/tests/components/wallbox/test_number.py index 3aba0792baa..cb332d1cb1e 100644 --- a/tests/components/wallbox/test_number.py +++ b/tests/components/wallbox/test_number.py @@ -11,17 +11,12 @@ from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError -from .conftest import ( - http_403_error, - http_404_error, - http_429_error, - setup_integration, - test_response_bidir, -) +from .conftest import http_403_error, http_404_error, http_429_error, setup_integration from .const import ( MOCK_NUMBER_ENTITY_ENERGY_PRICE_ID, MOCK_NUMBER_ENTITY_ICP_CURRENT_ID, MOCK_NUMBER_ENTITY_ID, + WALLBOX_STATUS_RESPONSE_BIDIR, ) from tests.common import MockConfigEntry @@ -53,7 +48,7 @@ async def test_wallbox_number_power_class_bidir( ) -> None: """Test wallbox sensor class.""" with patch.object( - mock_wallbox, "getChargerStatus", return_value=test_response_bidir + mock_wallbox, "getChargerStatus", return_value=WALLBOX_STATUS_RESPONSE_BIDIR ): await setup_integration(hass, entry) diff --git a/tests/components/wallbox/test_select.py b/tests/components/wallbox/test_select.py index f194566dbae..c07d0ad5272 100644 --- a/tests/components/wallbox/test_select.py +++ b/tests/components/wallbox/test_select.py @@ -13,23 +13,21 @@ from homeassistant.components.wallbox.const import EcoSmartMode from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import HomeAssistant, HomeAssistantError -from .conftest import ( - http_404_error, - http_429_error, - setup_integration, - test_response, - test_response_eco_mode, - test_response_full_solar, - test_response_no_power_boost, +from .conftest import http_404_error, http_429_error, setup_integration +from .const import ( + MOCK_SELECT_ENTITY_ID, + WALLBOX_STATUS_RESPONSE, + WALLBOX_STATUS_RESPONSE_ECO_MODE, + WALLBOX_STATUS_RESPONSE_FULL_SOLAR, + WALLBOX_STATUS_RESPONSE_NO_POWER_BOOST, ) -from .const import MOCK_SELECT_ENTITY_ID from tests.common import MockConfigEntry TEST_OPTIONS = [ - (EcoSmartMode.OFF, test_response), - (EcoSmartMode.ECO_MODE, test_response_eco_mode), - (EcoSmartMode.FULL_SOLAR, test_response_full_solar), + (EcoSmartMode.OFF, WALLBOX_STATUS_RESPONSE), + (EcoSmartMode.ECO_MODE, WALLBOX_STATUS_RESPONSE_ECO_MODE), + (EcoSmartMode.FULL_SOLAR, WALLBOX_STATUS_RESPONSE_FULL_SOLAR), ] @@ -61,7 +59,9 @@ async def test_wallbox_select_no_power_boost_class( """Test wallbox select class.""" with patch.object( - mock_wallbox, "getChargerStatus", return_value=test_response_no_power_boost + mock_wallbox, + "getChargerStatus", + return_value=WALLBOX_STATUS_RESPONSE_NO_POWER_BOOST, ): await setup_integration(hass, entry)