From 99e61a08fd0b9e394ce11171968682f8069bb104 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Fri, 8 Nov 2024 23:08:04 +0000 Subject: [PATCH 1/4] Add get_price service to Nord Pool --- homeassistant/components/nordpool/__init__.py | 12 ++ homeassistant/components/nordpool/icons.json | 5 + homeassistant/components/nordpool/services.py | 121 ++++++++++++++++++ .../components/nordpool/services.yaml | 26 ++++ .../components/nordpool/strings.json | 47 +++++++ 5 files changed, 211 insertions(+) create mode 100644 homeassistant/components/nordpool/services.py create mode 100644 homeassistant/components/nordpool/services.yaml diff --git a/homeassistant/components/nordpool/__init__.py b/homeassistant/components/nordpool/__init__.py index 82db98e2148d2..8a04f2067c457 100644 --- a/homeassistant/components/nordpool/__init__.py +++ b/homeassistant/components/nordpool/__init__.py @@ -5,13 +5,25 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.typing import ConfigType from homeassistant.util import dt as dt_util from .const import DOMAIN, PLATFORMS from .coordinator import NordPoolDataUpdateCoordinator +from .services import async_setup_services type NordPoolConfigEntry = ConfigEntry[NordPoolDataUpdateCoordinator] +CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) + + +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up the Habitica service.""" + + async_setup_services(hass) + return True + async def async_setup_entry(hass: HomeAssistant, entry: NordPoolConfigEntry) -> bool: """Set up Nord Pool from a config entry.""" diff --git a/homeassistant/components/nordpool/icons.json b/homeassistant/components/nordpool/icons.json index 85434a2d09b61..5a1a3df3d921b 100644 --- a/homeassistant/components/nordpool/icons.json +++ b/homeassistant/components/nordpool/icons.json @@ -38,5 +38,10 @@ "default": "mdi:cash-multiple" } } + }, + "services": { + "get_prices_for_date": { + "service": "mdi:cash-multiple" + } } } diff --git a/homeassistant/components/nordpool/services.py b/homeassistant/components/nordpool/services.py new file mode 100644 index 0000000000000..df752f1cadb03 --- /dev/null +++ b/homeassistant/components/nordpool/services.py @@ -0,0 +1,121 @@ +"""Services for Nord Pool integration.""" + +from __future__ import annotations + +from datetime import date, datetime, timedelta +import logging +from typing import TYPE_CHECKING + +from pynordpool import Currency +import voluptuous as vol + +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import ATTR_DATE +from homeassistant.core import ( + HomeAssistant, + ServiceCall, + ServiceResponse, + SupportsResponse, +) +from homeassistant.exceptions import ServiceValidationError +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.selector import ConfigEntrySelector +from homeassistant.util import dt as dt_util +from homeassistant.util.json import JsonValueType + +if TYPE_CHECKING: + from . import NordPoolConfigEntry +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) +ATTR_CONFIG_ENTRY = "config_entry" +ATTR_AREAS = "areas" +ATTR_CURRENCY = "currency" + +SERVICE_GET_PRICES_FOR_DATE = "get_prices_for_date" +SERVICE_GET_PRICES_SCHEMA = vol.Schema( + { + vol.Required(ATTR_CONFIG_ENTRY): ConfigEntrySelector(), + vol.Required(ATTR_DATE): cv.date, + vol.Optional(ATTR_AREAS, default=[]): cv.ensure_list, + vol.Optional(ATTR_CURRENCY): cv.string, + } +) + + +def get_config_entry(hass: HomeAssistant, entry_id: str) -> NordPoolConfigEntry: + """Return config entry.""" + if not (entry := hass.config_entries.async_get_entry(entry_id)): + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="entry_not_found", + ) + if entry.state is not ConfigEntryState.LOADED: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="entry_not_loaded", + ) + return entry + + +def async_setup_services(hass: HomeAssistant) -> None: + """Set up services for Nord Pool integration.""" + + async def get_prices_for_date(call: ServiceCall) -> ServiceResponse: + """Get price service.""" + entry = get_config_entry(hass, call.data[ATTR_CONFIG_ENTRY]) + asked_date: date = call.data[ATTR_DATE] + client = entry.runtime_data.client + areas = call.data.get(ATTR_AREAS) + if not areas: + areas = entry.data[ATTR_AREAS] + currency = call.data.get(ATTR_CURRENCY) + if not currency: + currency = entry.data[ATTR_CURRENCY] + + areas = [area.upper() for area in areas] + currency = currency.upper() + + today = dt_util.utcnow().date() + if asked_date.month - 2 < 1: + month = 12 + (today.month - 2) + past_date_valid = date(today.year - 1, month, today.day) + else: + past_date_valid = date(today.year, today.month - 2, today.day) + + if asked_date < past_date_valid: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="date_to_far_in_past", + ) + if asked_date > (date.today() + timedelta(days=1)): + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="date_to_far_in_future", + ) + + # Change to Auth error + Emtpty response + price_data = await client.async_get_delivery_period( + datetime.combine(asked_date, dt_util.utcnow().time()), + Currency(currency), + areas, + ) + result: dict[str, JsonValueType] = {} + for area in areas: + result[area] = [ + { + "start": price_entry.start.isoformat(), + "end": price_entry.end.isoformat(), + "price": price_entry.entry[area], + } + for price_entry in price_data.entries + ] + return result + + hass.services.async_register( + DOMAIN, + SERVICE_GET_PRICES_FOR_DATE, + get_prices_for_date, + schema=SERVICE_GET_PRICES_SCHEMA, + supports_response=SupportsResponse.ONLY, + ) diff --git a/homeassistant/components/nordpool/services.yaml b/homeassistant/components/nordpool/services.yaml new file mode 100644 index 0000000000000..aa8b5a29471d0 --- /dev/null +++ b/homeassistant/components/nordpool/services.yaml @@ -0,0 +1,26 @@ +get_prices_for_date: + fields: + config_entry: + required: true + selector: + config_entry: + integration: nordpool + date: + required: true + selector: + date: + areas: + selector: + text: + multiple: true + currency: + selector: + select: + options: + - "DKK" + - "EUR" + - "NOK" + - "PLN" + - "SEK" + mode: dropdown + translation_key: "currency" diff --git a/homeassistant/components/nordpool/strings.json b/homeassistant/components/nordpool/strings.json index 1a4551fe61afd..b95838284c1b9 100644 --- a/homeassistant/components/nordpool/strings.json +++ b/homeassistant/components/nordpool/strings.json @@ -62,9 +62,56 @@ } } }, + "services": { + "get_prices_for_date": { + "name": "Get prices for date", + "description": "Retrieve the prices for a specific date.", + "fields": { + "config_entry": { + "name": "Select Nord Pool configuration entry", + "description": "Choose the configuration entry." + }, + "date": { + "name": "Date", + "description": "Only dates two months in the past and one day in the future is allowed." + }, + "areas": { + "name": "Areas", + "description": "One or multiple areas to get prices for. If left empty it will use the areas already configured." + }, + "currency": { + "name": "Currency", + "description": "Currency to get prices in. If left empty it will use the currency already configured." + } + } + } + }, + "selector": { + "currency": { + "options": { + "dkk": "DKK", + "eur": "EUR", + "nok": "NOK", + "pln": "PLN", + "sek": "SEK" + } + } + }, "exceptions": { "initial_update_failed": { "message": "Initial update failed on startup with error {error}" + }, + "entry_not_found": { + "message": "The selected character is not configured in Home Assistant." + }, + "entry_not_loaded": { + "message": "The selected character is currently not loaded or disabled in Home Assistant." + }, + "date_to_far_in_past": { + "message": "Only dates two months in the past is allowed." + }, + "date_to_far_in_future": { + "message": "Only dates one day in the future is allowed." } } } From 433469d1b9fd11c1c654aaf1662f2b4656f14c4d Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 9 Nov 2024 23:27:10 +0000 Subject: [PATCH 2/4] Tests and fixes --- .../components/nordpool/strings.json | 4 +- .../nordpool/snapshots/test_services.ambr | 249 ++++++++++++++++++ tests/components/nordpool/test_services.py | 95 +++++++ 3 files changed, 346 insertions(+), 2 deletions(-) create mode 100644 tests/components/nordpool/snapshots/test_services.ambr create mode 100644 tests/components/nordpool/test_services.py diff --git a/homeassistant/components/nordpool/strings.json b/homeassistant/components/nordpool/strings.json index b95838284c1b9..74d0c15cd854a 100644 --- a/homeassistant/components/nordpool/strings.json +++ b/homeassistant/components/nordpool/strings.json @@ -102,10 +102,10 @@ "message": "Initial update failed on startup with error {error}" }, "entry_not_found": { - "message": "The selected character is not configured in Home Assistant." + "message": "The Nord Pool integration is not configured in Home Assistant." }, "entry_not_loaded": { - "message": "The selected character is currently not loaded or disabled in Home Assistant." + "message": "The Nord Pool integration is currently not loaded or disabled in Home Assistant." }, "date_to_far_in_past": { "message": "Only dates two months in the past is allowed." diff --git a/tests/components/nordpool/snapshots/test_services.ambr b/tests/components/nordpool/snapshots/test_services.ambr new file mode 100644 index 0000000000000..da38e785a4c33 --- /dev/null +++ b/tests/components/nordpool/snapshots/test_services.ambr @@ -0,0 +1,249 @@ +# serializer version: 1 +# name: test_service_call + dict({ + 'SE3': list([ + dict({ + 'end': '2024-11-05T00:00:00+00:00', + 'price': 250.73, + 'start': '2024-11-04T23:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T01:00:00+00:00', + 'price': 76.36, + 'start': '2024-11-05T00:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T02:00:00+00:00', + 'price': 73.92, + 'start': '2024-11-05T01:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T03:00:00+00:00', + 'price': 61.69, + 'start': '2024-11-05T02:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T04:00:00+00:00', + 'price': 64.6, + 'start': '2024-11-05T03:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T05:00:00+00:00', + 'price': 453.27, + 'start': '2024-11-05T04:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T06:00:00+00:00', + 'price': 996.28, + 'start': '2024-11-05T05:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T07:00:00+00:00', + 'price': 1406.14, + 'start': '2024-11-05T06:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T08:00:00+00:00', + 'price': 1346.54, + 'start': '2024-11-05T07:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T09:00:00+00:00', + 'price': 1150.28, + 'start': '2024-11-05T08:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T10:00:00+00:00', + 'price': 1031.32, + 'start': '2024-11-05T09:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T11:00:00+00:00', + 'price': 927.37, + 'start': '2024-11-05T10:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T12:00:00+00:00', + 'price': 925.05, + 'start': '2024-11-05T11:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T13:00:00+00:00', + 'price': 949.49, + 'start': '2024-11-05T12:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T14:00:00+00:00', + 'price': 1042.03, + 'start': '2024-11-05T13:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T15:00:00+00:00', + 'price': 1258.89, + 'start': '2024-11-05T14:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T16:00:00+00:00', + 'price': 1816.45, + 'start': '2024-11-05T15:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T17:00:00+00:00', + 'price': 2512.65, + 'start': '2024-11-05T16:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T18:00:00+00:00', + 'price': 1819.83, + 'start': '2024-11-05T17:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T19:00:00+00:00', + 'price': 1011.77, + 'start': '2024-11-05T18:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T20:00:00+00:00', + 'price': 835.53, + 'start': '2024-11-05T19:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T21:00:00+00:00', + 'price': 796.19, + 'start': '2024-11-05T20:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T22:00:00+00:00', + 'price': 522.3, + 'start': '2024-11-05T21:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T23:00:00+00:00', + 'price': 289.14, + 'start': '2024-11-05T22:00:00+00:00', + }), + ]), + 'SE4': list([ + dict({ + 'end': '2024-11-05T00:00:00+00:00', + 'price': 283.79, + 'start': '2024-11-04T23:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T01:00:00+00:00', + 'price': 81.36, + 'start': '2024-11-05T00:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T02:00:00+00:00', + 'price': 79.15, + 'start': '2024-11-05T01:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T03:00:00+00:00', + 'price': 65.19, + 'start': '2024-11-05T02:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T04:00:00+00:00', + 'price': 68.44, + 'start': '2024-11-05T03:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T05:00:00+00:00', + 'price': 516.71, + 'start': '2024-11-05T04:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T06:00:00+00:00', + 'price': 1240.85, + 'start': '2024-11-05T05:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T07:00:00+00:00', + 'price': 1648.25, + 'start': '2024-11-05T06:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T08:00:00+00:00', + 'price': 1570.5, + 'start': '2024-11-05T07:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T09:00:00+00:00', + 'price': 1345.37, + 'start': '2024-11-05T08:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T10:00:00+00:00', + 'price': 1206.51, + 'start': '2024-11-05T09:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T11:00:00+00:00', + 'price': 1085.8, + 'start': '2024-11-05T10:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T12:00:00+00:00', + 'price': 1081.72, + 'start': '2024-11-05T11:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T13:00:00+00:00', + 'price': 1130.38, + 'start': '2024-11-05T12:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T14:00:00+00:00', + 'price': 1256.91, + 'start': '2024-11-05T13:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T15:00:00+00:00', + 'price': 1765.82, + 'start': '2024-11-05T14:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T16:00:00+00:00', + 'price': 2522.55, + 'start': '2024-11-05T15:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T17:00:00+00:00', + 'price': 3533.03, + 'start': '2024-11-05T16:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T18:00:00+00:00', + 'price': 2524.06, + 'start': '2024-11-05T17:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T19:00:00+00:00', + 'price': 1804.46, + 'start': '2024-11-05T18:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T20:00:00+00:00', + 'price': 1112.57, + 'start': '2024-11-05T19:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T21:00:00+00:00', + 'price': 1051.69, + 'start': '2024-11-05T20:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T22:00:00+00:00', + 'price': 662.44, + 'start': '2024-11-05T21:00:00+00:00', + }), + dict({ + 'end': '2024-11-05T23:00:00+00:00', + 'price': 349.21, + 'start': '2024-11-05T22:00:00+00:00', + }), + ]), + }) +# --- diff --git a/tests/components/nordpool/test_services.py b/tests/components/nordpool/test_services.py new file mode 100644 index 0000000000000..d0ed118376f7d --- /dev/null +++ b/tests/components/nordpool/test_services.py @@ -0,0 +1,95 @@ +"""Test services in Nord Pool.""" + +from unittest.mock import patch + +from pynordpool import DeliveryPeriodData +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.nordpool.const import DOMAIN +from homeassistant.components.nordpool.services import ( + ATTR_AREAS, + ATTR_CONFIG_ENTRY, + ATTR_CURRENCY, + SERVICE_GET_PRICES_FOR_DATE, +) +from homeassistant.const import ATTR_DATE +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ServiceValidationError + +from tests.common import MockConfigEntry + +TEST_SERVICE_DATA = { + ATTR_CONFIG_ENTRY: "to_replace", + ATTR_DATE: "2024-11-05", + ATTR_AREAS: ["SE3", "SE4"], + ATTR_CURRENCY: "SEK", +} + + +@pytest.mark.freeze_time("2024-11-05T18:00:00+00:00") +async def test_service_call( + hass: HomeAssistant, + load_int: MockConfigEntry, + get_data: DeliveryPeriodData, + snapshot: SnapshotAssertion, +) -> None: + """Test get_prices_for_date service call.""" + + with ( + patch( + "homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period", + return_value=get_data, + ), + ): + service_data = TEST_SERVICE_DATA.copy() + service_data[ATTR_CONFIG_ENTRY] = load_int.entry_id + response = await hass.services.async_call( + DOMAIN, + SERVICE_GET_PRICES_FOR_DATE, + service_data, + blocking=True, + return_response=True, + ) + + assert response == snapshot + + +@pytest.mark.freeze_time("2024-11-05T18:00:00+00:00") +async def test_service_call_failures( + hass: HomeAssistant, + load_int: MockConfigEntry, + get_data: DeliveryPeriodData, + snapshot: SnapshotAssertion, +) -> None: + """Test get_prices_for_date service call when it fails.""" + + with ( + patch( + "homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period", + return_value=get_data, + ), + pytest.raises(ServiceValidationError), + ): + await hass.services.async_call( + DOMAIN, + SERVICE_GET_PRICES_FOR_DATE, + TEST_SERVICE_DATA, + blocking=True, + return_response=True, + ) + + with ( + patch( + "homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period", + return_value=get_data, + ), + pytest.raises(ServiceValidationError), + ): + await hass.services.async_call( + DOMAIN, + SERVICE_GET_PRICES_FOR_DATE, + TEST_SERVICE_DATA, + blocking=True, + return_response=True, + ) From 138d4d94d546d2e179f9374d87c0a0a7b9305f02 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sun, 10 Nov 2024 11:19:46 +0000 Subject: [PATCH 3/4] Fixes --- homeassistant/components/nordpool/services.py | 52 +++++++----- .../components/nordpool/strings.json | 11 ++- tests/components/nordpool/test_services.py | 83 ++++++++++++++++++- 3 files changed, 115 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/nordpool/services.py b/homeassistant/components/nordpool/services.py index df752f1cadb03..fa58edcf1ee41 100644 --- a/homeassistant/components/nordpool/services.py +++ b/homeassistant/components/nordpool/services.py @@ -2,11 +2,16 @@ from __future__ import annotations -from datetime import date, datetime, timedelta +from datetime import date, datetime import logging from typing import TYPE_CHECKING -from pynordpool import Currency +from pynordpool import ( + Currency, + NordPoolAuthenticationError, + NordPoolEmptyResponseError, + NordPoolError, +) import voluptuous as vol from homeassistant.config_entries import ConfigEntryState @@ -66,40 +71,41 @@ async def get_prices_for_date(call: ServiceCall) -> ServiceResponse: entry = get_config_entry(hass, call.data[ATTR_CONFIG_ENTRY]) asked_date: date = call.data[ATTR_DATE] client = entry.runtime_data.client - areas = call.data.get(ATTR_AREAS) + areas: list[str] | None = call.data.get(ATTR_AREAS) if not areas: areas = entry.data[ATTR_AREAS] - currency = call.data.get(ATTR_CURRENCY) + currency: str | None = call.data.get(ATTR_CURRENCY) if not currency: currency = entry.data[ATTR_CURRENCY] + if TYPE_CHECKING: + assert isinstance(areas, list) + assert isinstance(currency, str) areas = [area.upper() for area in areas] currency = currency.upper() - today = dt_util.utcnow().date() - if asked_date.month - 2 < 1: - month = 12 + (today.month - 2) - past_date_valid = date(today.year - 1, month, today.day) - else: - past_date_valid = date(today.year, today.month - 2, today.day) - - if asked_date < past_date_valid: + try: + price_data = await client.async_get_delivery_period( + datetime.combine(asked_date, dt_util.utcnow().time()), + Currency(currency), + areas, + ) + except NordPoolAuthenticationError as error: raise ServiceValidationError( translation_domain=DOMAIN, - translation_key="date_to_far_in_past", - ) - if asked_date > (date.today() + timedelta(days=1)): + translation_key="authentication_error", + ) from error + except NordPoolEmptyResponseError as error: raise ServiceValidationError( translation_domain=DOMAIN, - translation_key="date_to_far_in_future", - ) + translation_key="empty_response", + ) from error + except NordPoolError as error: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="connection_error", + ) from error - # Change to Auth error + Emtpty response - price_data = await client.async_get_delivery_period( - datetime.combine(asked_date, dt_util.utcnow().time()), - Currency(currency), - areas, - ) result: dict[str, JsonValueType] = {} for area in areas: result[area] = [ diff --git a/homeassistant/components/nordpool/strings.json b/homeassistant/components/nordpool/strings.json index 74d0c15cd854a..4ab4721e82b3b 100644 --- a/homeassistant/components/nordpool/strings.json +++ b/homeassistant/components/nordpool/strings.json @@ -107,11 +107,14 @@ "entry_not_loaded": { "message": "The Nord Pool integration is currently not loaded or disabled in Home Assistant." }, - "date_to_far_in_past": { - "message": "Only dates two months in the past is allowed." + "authentication_error": { + "message": "There was an authentication error as you tried to retrieve data too far in the past." }, - "date_to_far_in_future": { - "message": "Only dates one day in the future is allowed." + "empty_response": { + "message": "Nord Pool has not posted market prices for the provided date." + }, + "connection_error": { + "message": "There was a connection error connecting to the API. Try again later." } } } diff --git a/tests/components/nordpool/test_services.py b/tests/components/nordpool/test_services.py index d0ed118376f7d..98428ac19b4da 100644 --- a/tests/components/nordpool/test_services.py +++ b/tests/components/nordpool/test_services.py @@ -2,7 +2,12 @@ from unittest.mock import patch -from pynordpool import DeliveryPeriodData +from pynordpool import ( + DeliveryPeriodData, + NordPoolAuthenticationError, + NordPoolEmptyResponseError, + NordPoolError, +) import pytest from syrupy.assertion import SnapshotAssertion @@ -25,6 +30,10 @@ ATTR_AREAS: ["SE3", "SE4"], ATTR_CURRENCY: "SEK", } +TEST_SERVICE_DATA_USE_DEFAULTS = { + ATTR_CONFIG_ENTRY: "to_replace", + ATTR_DATE: "2024-11-05", +} @pytest.mark.freeze_time("2024-11-05T18:00:00+00:00") @@ -53,23 +62,82 @@ async def test_service_call( ) assert response == snapshot + price_value = response["SE3"][0]["price"] + with ( + patch( + "homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period", + return_value=get_data, + ), + ): + service_data = TEST_SERVICE_DATA_USE_DEFAULTS.copy() + service_data[ATTR_CONFIG_ENTRY] = load_int.entry_id + response = await hass.services.async_call( + DOMAIN, + SERVICE_GET_PRICES_FOR_DATE, + service_data, + blocking=True, + return_response=True, + ) + assert "SE3" in response + assert "SE4" in response + assert response["SE3"][0]["price"] == price_value + + +@pytest.mark.parametrize( + ("error", "key"), + [ + (NordPoolAuthenticationError, "authentication_error"), + (NordPoolEmptyResponseError, "empty_response"), + (NordPoolError, "connection_error"), + ], +) @pytest.mark.freeze_time("2024-11-05T18:00:00+00:00") async def test_service_call_failures( hass: HomeAssistant, load_int: MockConfigEntry, get_data: DeliveryPeriodData, snapshot: SnapshotAssertion, + error: Exception, + key: str, ) -> None: """Test get_prices_for_date service call when it fails.""" + service_data = TEST_SERVICE_DATA.copy() + service_data[ATTR_CONFIG_ENTRY] = load_int.entry_id + + with ( + patch( + "homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period", + side_effect=error, + ), + pytest.raises(ServiceValidationError) as err, + ): + await hass.services.async_call( + DOMAIN, + SERVICE_GET_PRICES_FOR_DATE, + service_data, + blocking=True, + return_response=True, + ) + assert err.value.translation_key == key + + +@pytest.mark.freeze_time("2024-11-05T18:00:00+00:00") +async def test_service_call_config_entry_bad_state( + hass: HomeAssistant, + load_int: MockConfigEntry, + get_data: DeliveryPeriodData, + snapshot: SnapshotAssertion, +) -> None: + """Test get_prices_for_date service call when config entry bad state.""" with ( patch( "homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period", return_value=get_data, ), - pytest.raises(ServiceValidationError), + pytest.raises(ServiceValidationError) as err, ): await hass.services.async_call( DOMAIN, @@ -78,18 +146,25 @@ async def test_service_call_failures( blocking=True, return_response=True, ) + assert err.value.translation_key == "entry_not_found" + + service_data = TEST_SERVICE_DATA.copy() + service_data[ATTR_CONFIG_ENTRY] = load_int.entry_id + await hass.config_entries.async_unload(load_int.entry_id) + await hass.async_block_till_done() with ( patch( "homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period", return_value=get_data, ), - pytest.raises(ServiceValidationError), + pytest.raises(ServiceValidationError) as err, ): await hass.services.async_call( DOMAIN, SERVICE_GET_PRICES_FOR_DATE, - TEST_SERVICE_DATA, + service_data, blocking=True, return_response=True, ) + assert err.value.translation_key == "entry_not_loaded" From 4e3f6ef7946784817ab86589ca1627c3cecb5c67 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sun, 10 Nov 2024 11:20:56 +0000 Subject: [PATCH 4/4] Not used fixtures --- tests/components/nordpool/test_services.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/components/nordpool/test_services.py b/tests/components/nordpool/test_services.py index 98428ac19b4da..ec5422e621792 100644 --- a/tests/components/nordpool/test_services.py +++ b/tests/components/nordpool/test_services.py @@ -97,8 +97,6 @@ async def test_service_call( async def test_service_call_failures( hass: HomeAssistant, load_int: MockConfigEntry, - get_data: DeliveryPeriodData, - snapshot: SnapshotAssertion, error: Exception, key: str, ) -> None: @@ -128,7 +126,6 @@ async def test_service_call_config_entry_bad_state( hass: HomeAssistant, load_int: MockConfigEntry, get_data: DeliveryPeriodData, - snapshot: SnapshotAssertion, ) -> None: """Test get_prices_for_date service call when config entry bad state."""