Load data for multiple days in Nord Pool (#133371)

* Load data for multiple days in Nord Pool

* Fix current day

* Fix tests

* Fix services

* Fix fixtures

* Mod get_data_current_day

* Mods

* simplify further
pull/133825/head^2
G Johansson 2024-12-22 21:10:12 +01:00 committed by GitHub
parent 26180486e7
commit 368e958457
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 1582 additions and 462 deletions

View File

@ -10,6 +10,8 @@ from typing import TYPE_CHECKING
from pynordpool import ( from pynordpool import (
Currency, Currency,
DeliveryPeriodData, DeliveryPeriodData,
DeliveryPeriodEntry,
DeliveryPeriodsData,
NordPoolClient, NordPoolClient,
NordPoolEmptyResponseError, NordPoolEmptyResponseError,
NordPoolError, NordPoolError,
@ -29,7 +31,7 @@ if TYPE_CHECKING:
from . import NordPoolConfigEntry from . import NordPoolConfigEntry
class NordPoolDataUpdateCoordinator(DataUpdateCoordinator[DeliveryPeriodData]): class NordPoolDataUpdateCoordinator(DataUpdateCoordinator[DeliveryPeriodsData]):
"""A Nord Pool Data Update Coordinator.""" """A Nord Pool Data Update Coordinator."""
config_entry: NordPoolConfigEntry config_entry: NordPoolConfigEntry
@ -74,12 +76,16 @@ class NordPoolDataUpdateCoordinator(DataUpdateCoordinator[DeliveryPeriodData]):
if data: if data:
self.async_set_updated_data(data) self.async_set_updated_data(data)
async def api_call(self, retry: int = 3) -> DeliveryPeriodData | None: async def api_call(self, retry: int = 3) -> DeliveryPeriodsData | None:
"""Make api call to retrieve data with retry if failure.""" """Make api call to retrieve data with retry if failure."""
data = None data = None
try: try:
data = await self.client.async_get_delivery_period( data = await self.client.async_get_delivery_periods(
dt_util.now(), [
dt_util.now() - timedelta(days=1),
dt_util.now(),
dt_util.now() + timedelta(days=1),
],
Currency(self.config_entry.data[CONF_CURRENCY]), Currency(self.config_entry.data[CONF_CURRENCY]),
self.config_entry.data[CONF_AREAS], self.config_entry.data[CONF_AREAS],
) )
@ -97,3 +103,20 @@ class NordPoolDataUpdateCoordinator(DataUpdateCoordinator[DeliveryPeriodData]):
self.async_set_update_error(error) self.async_set_update_error(error)
return data return data
def merge_price_entries(self) -> list[DeliveryPeriodEntry]:
"""Return the merged price entries."""
merged_entries: list[DeliveryPeriodEntry] = []
for del_period in self.data.entries:
merged_entries.extend(del_period.entries)
return merged_entries
def get_data_current_day(self) -> DeliveryPeriodData:
"""Return the current day data."""
current_day = dt_util.utcnow().strftime("%Y-%m-%d")
delivery_period: DeliveryPeriodData = self.data.entries[0]
for del_period in self.data.entries:
if del_period.requested_date == current_day:
delivery_period = del_period
break
return delivery_period

View File

@ -6,8 +6,6 @@ from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timedelta from datetime import datetime, timedelta
from pynordpool import DeliveryPeriodData
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
EntityCategory, EntityCategory,
SensorDeviceClass, SensorDeviceClass,
@ -29,34 +27,34 @@ PARALLEL_UPDATES = 0
def validate_prices( def validate_prices(
func: Callable[ func: Callable[
[DeliveryPeriodData], dict[str, tuple[float | None, float, float | None]] [NordpoolPriceSensor], dict[str, tuple[float | None, float, float | None]]
], ],
data: DeliveryPeriodData, entity: NordpoolPriceSensor,
area: str, area: str,
index: int, index: int,
) -> float | None: ) -> float | None:
"""Validate and return.""" """Validate and return."""
if result := func(data)[area][index]: if result := func(entity)[area][index]:
return result / 1000 return result / 1000
return None return None
def get_prices( def get_prices(
data: DeliveryPeriodData, entity: NordpoolPriceSensor,
) -> dict[str, tuple[float | None, float, float | None]]: ) -> dict[str, tuple[float | None, float, float | None]]:
"""Return previous, current and next prices. """Return previous, current and next prices.
Output: {"SE3": (10.0, 10.5, 12.1)} Output: {"SE3": (10.0, 10.5, 12.1)}
""" """
data = entity.coordinator.merge_price_entries()
last_price_entries: dict[str, float] = {} last_price_entries: dict[str, float] = {}
current_price_entries: dict[str, float] = {} current_price_entries: dict[str, float] = {}
next_price_entries: dict[str, float] = {} next_price_entries: dict[str, float] = {}
current_time = dt_util.utcnow() current_time = dt_util.utcnow()
previous_time = current_time - timedelta(hours=1) previous_time = current_time - timedelta(hours=1)
next_time = current_time + timedelta(hours=1) next_time = current_time + timedelta(hours=1)
price_data = data.entries LOGGER.debug("Price data: %s", data)
LOGGER.debug("Price data: %s", price_data) for entry in data:
for entry in price_data:
if entry.start <= current_time <= entry.end: if entry.start <= current_time <= entry.end:
current_price_entries = entry.entry current_price_entries = entry.entry
if entry.start <= previous_time <= entry.end: if entry.start <= previous_time <= entry.end:
@ -82,11 +80,12 @@ def get_prices(
def get_min_max_price( def get_min_max_price(
data: DeliveryPeriodData, entity: NordpoolPriceSensor,
area: str,
func: Callable[[float, float], float], func: Callable[[float, float], float],
) -> tuple[float, datetime, datetime]: ) -> tuple[float, datetime, datetime]:
"""Get the lowest price from the data.""" """Get the lowest price from the data."""
data = entity.coordinator.get_data_current_day()
area = entity.area
price_data = data.entries price_data = data.entries
price: float = price_data[0].entry[area] price: float = price_data[0].entry[area]
start: datetime = price_data[0].start start: datetime = price_data[0].start
@ -102,12 +101,13 @@ def get_min_max_price(
def get_blockprices( def get_blockprices(
data: DeliveryPeriodData, entity: NordpoolBlockPriceSensor,
) -> dict[str, dict[str, tuple[datetime, datetime, float, float, float]]]: ) -> dict[str, dict[str, tuple[datetime, datetime, float, float, float]]]:
"""Return average, min and max for block prices. """Return average, min and max for block prices.
Output: {"SE3": {"Off-peak 1": (_datetime_, _datetime_, 9.3, 10.5, 12.1)}} Output: {"SE3": {"Off-peak 1": (_datetime_, _datetime_, 9.3, 10.5, 12.1)}}
""" """
data = entity.coordinator.get_data_current_day()
result: dict[str, dict[str, tuple[datetime, datetime, float, float, float]]] = {} result: dict[str, dict[str, tuple[datetime, datetime, float, float, float]]] = {}
block_prices = data.block_prices block_prices = data.block_prices
for entry in block_prices: for entry in block_prices:
@ -130,15 +130,15 @@ def get_blockprices(
class NordpoolDefaultSensorEntityDescription(SensorEntityDescription): class NordpoolDefaultSensorEntityDescription(SensorEntityDescription):
"""Describes Nord Pool default sensor entity.""" """Describes Nord Pool default sensor entity."""
value_fn: Callable[[DeliveryPeriodData], str | float | datetime | None] value_fn: Callable[[NordpoolSensor], str | float | datetime | None]
@dataclass(frozen=True, kw_only=True) @dataclass(frozen=True, kw_only=True)
class NordpoolPricesSensorEntityDescription(SensorEntityDescription): class NordpoolPricesSensorEntityDescription(SensorEntityDescription):
"""Describes Nord Pool prices sensor entity.""" """Describes Nord Pool prices sensor entity."""
value_fn: Callable[[DeliveryPeriodData, str], float | None] value_fn: Callable[[NordpoolPriceSensor], float | None]
extra_fn: Callable[[DeliveryPeriodData, str], dict[str, str] | None] extra_fn: Callable[[NordpoolPriceSensor], dict[str, str] | None]
@dataclass(frozen=True, kw_only=True) @dataclass(frozen=True, kw_only=True)
@ -155,19 +155,19 @@ DEFAULT_SENSOR_TYPES: tuple[NordpoolDefaultSensorEntityDescription, ...] = (
key="updated_at", key="updated_at",
translation_key="updated_at", translation_key="updated_at",
device_class=SensorDeviceClass.TIMESTAMP, device_class=SensorDeviceClass.TIMESTAMP,
value_fn=lambda data: data.updated_at, value_fn=lambda entity: entity.coordinator.get_data_current_day().updated_at,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
), ),
NordpoolDefaultSensorEntityDescription( NordpoolDefaultSensorEntityDescription(
key="currency", key="currency",
translation_key="currency", translation_key="currency",
value_fn=lambda data: data.currency, value_fn=lambda entity: entity.coordinator.get_data_current_day().currency,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
), ),
NordpoolDefaultSensorEntityDescription( NordpoolDefaultSensorEntityDescription(
key="exchange_rate", key="exchange_rate",
translation_key="exchange_rate", translation_key="exchange_rate",
value_fn=lambda data: data.exchange_rate, value_fn=lambda entity: entity.coordinator.get_data_current_day().exchange_rate,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
@ -177,42 +177,42 @@ PRICES_SENSOR_TYPES: tuple[NordpoolPricesSensorEntityDescription, ...] = (
NordpoolPricesSensorEntityDescription( NordpoolPricesSensorEntityDescription(
key="current_price", key="current_price",
translation_key="current_price", translation_key="current_price",
value_fn=lambda data, area: validate_prices(get_prices, data, area, 1), value_fn=lambda entity: validate_prices(get_prices, entity, entity.area, 1),
extra_fn=lambda data, area: None, extra_fn=lambda entity: None,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=2, suggested_display_precision=2,
), ),
NordpoolPricesSensorEntityDescription( NordpoolPricesSensorEntityDescription(
key="last_price", key="last_price",
translation_key="last_price", translation_key="last_price",
value_fn=lambda data, area: validate_prices(get_prices, data, area, 0), value_fn=lambda entity: validate_prices(get_prices, entity, entity.area, 0),
extra_fn=lambda data, area: None, extra_fn=lambda entity: None,
suggested_display_precision=2, suggested_display_precision=2,
), ),
NordpoolPricesSensorEntityDescription( NordpoolPricesSensorEntityDescription(
key="next_price", key="next_price",
translation_key="next_price", translation_key="next_price",
value_fn=lambda data, area: validate_prices(get_prices, data, area, 2), value_fn=lambda entity: validate_prices(get_prices, entity, entity.area, 2),
extra_fn=lambda data, area: None, extra_fn=lambda entity: None,
suggested_display_precision=2, suggested_display_precision=2,
), ),
NordpoolPricesSensorEntityDescription( NordpoolPricesSensorEntityDescription(
key="lowest_price", key="lowest_price",
translation_key="lowest_price", translation_key="lowest_price",
value_fn=lambda data, area: get_min_max_price(data, area, min)[0] / 1000, value_fn=lambda entity: get_min_max_price(entity, min)[0] / 1000,
extra_fn=lambda data, area: { extra_fn=lambda entity: {
"start": get_min_max_price(data, area, min)[1].isoformat(), "start": get_min_max_price(entity, min)[1].isoformat(),
"end": get_min_max_price(data, area, min)[2].isoformat(), "end": get_min_max_price(entity, min)[2].isoformat(),
}, },
suggested_display_precision=2, suggested_display_precision=2,
), ),
NordpoolPricesSensorEntityDescription( NordpoolPricesSensorEntityDescription(
key="highest_price", key="highest_price",
translation_key="highest_price", translation_key="highest_price",
value_fn=lambda data, area: get_min_max_price(data, area, max)[0] / 1000, value_fn=lambda entity: get_min_max_price(entity, max)[0] / 1000,
extra_fn=lambda data, area: { extra_fn=lambda entity: {
"start": get_min_max_price(data, area, max)[1].isoformat(), "start": get_min_max_price(entity, max)[1].isoformat(),
"end": get_min_max_price(data, area, max)[2].isoformat(), "end": get_min_max_price(entity, max)[2].isoformat(),
}, },
suggested_display_precision=2, suggested_display_precision=2,
), ),
@ -276,11 +276,12 @@ async def async_setup_entry(
"""Set up Nord Pool sensor platform.""" """Set up Nord Pool sensor platform."""
coordinator = entry.runtime_data coordinator = entry.runtime_data
current_day_data = entry.runtime_data.get_data_current_day()
entities: list[NordpoolBaseEntity] = [] entities: list[NordpoolBaseEntity] = []
currency = entry.runtime_data.data.currency currency = current_day_data.currency
for area in get_prices(entry.runtime_data.data): for area in current_day_data.area_average:
LOGGER.debug("Setting up base sensors for area %s", area) LOGGER.debug("Setting up base sensors for area %s", area)
entities.extend( entities.extend(
NordpoolSensor(coordinator, description, area) NordpoolSensor(coordinator, description, area)
@ -297,16 +298,16 @@ async def async_setup_entry(
NordpoolDailyAveragePriceSensor(coordinator, description, area, currency) NordpoolDailyAveragePriceSensor(coordinator, description, area, currency)
for description in DAILY_AVERAGE_PRICES_SENSOR_TYPES for description in DAILY_AVERAGE_PRICES_SENSOR_TYPES
) )
for block_name in get_blockprices(coordinator.data)[area]: for block_prices in entry.runtime_data.get_data_current_day().block_prices:
LOGGER.debug( LOGGER.debug(
"Setting up block price sensors for area %s with currency %s in block %s", "Setting up block price sensors for area %s with currency %s in block %s",
area, area,
currency, currency,
block_name, block_prices.name,
) )
entities.extend( entities.extend(
NordpoolBlockPriceSensor( NordpoolBlockPriceSensor(
coordinator, description, area, currency, block_name coordinator, description, area, currency, block_prices.name
) )
for description in BLOCK_PRICES_SENSOR_TYPES for description in BLOCK_PRICES_SENSOR_TYPES
) )
@ -321,7 +322,7 @@ class NordpoolSensor(NordpoolBaseEntity, SensorEntity):
@property @property
def native_value(self) -> str | float | datetime | None: def native_value(self) -> str | float | datetime | None:
"""Return value of sensor.""" """Return value of sensor."""
return self.entity_description.value_fn(self.coordinator.data) return self.entity_description.value_fn(self)
class NordpoolPriceSensor(NordpoolBaseEntity, SensorEntity): class NordpoolPriceSensor(NordpoolBaseEntity, SensorEntity):
@ -343,12 +344,12 @@ class NordpoolPriceSensor(NordpoolBaseEntity, SensorEntity):
@property @property
def native_value(self) -> float | None: def native_value(self) -> float | None:
"""Return value of sensor.""" """Return value of sensor."""
return self.entity_description.value_fn(self.coordinator.data, self.area) return self.entity_description.value_fn(self)
@property @property
def extra_state_attributes(self) -> dict[str, str] | None: def extra_state_attributes(self) -> dict[str, str] | None:
"""Return the extra state attributes.""" """Return the extra state attributes."""
return self.entity_description.extra_fn(self.coordinator.data, self.area) return self.entity_description.extra_fn(self)
class NordpoolBlockPriceSensor(NordpoolBaseEntity, SensorEntity): class NordpoolBlockPriceSensor(NordpoolBaseEntity, SensorEntity):
@ -376,7 +377,7 @@ class NordpoolBlockPriceSensor(NordpoolBaseEntity, SensorEntity):
def native_value(self) -> float | datetime | None: def native_value(self) -> float | datetime | None:
"""Return value of sensor.""" """Return value of sensor."""
return self.entity_description.value_fn( return self.entity_description.value_fn(
get_blockprices(self.coordinator.data)[self.area][self.block_name] get_blockprices(self)[self.area][self.block_name]
) )
@ -399,4 +400,5 @@ class NordpoolDailyAveragePriceSensor(NordpoolBaseEntity, SensorEntity):
@property @property
def native_value(self) -> float | None: def native_value(self) -> float | None:
"""Return value of sensor.""" """Return value of sensor."""
return self.coordinator.data.area_average[self.area] / 1000 data = self.coordinator.get_data_current_day()
return data.area_average[self.area] / 1000

View File

@ -3,20 +3,16 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import AsyncGenerator from collections.abc import AsyncGenerator
from datetime import datetime
import json import json
from typing import Any from typing import Any
from unittest.mock import patch from unittest.mock import patch
from pynordpool import NordPoolClient from pynordpool import API, NordPoolClient
from pynordpool.const import Currency
from pynordpool.model import DeliveryPeriodData
import pytest import pytest
from homeassistant.components.nordpool.const import DOMAIN from homeassistant.components.nordpool.const import DOMAIN
from homeassistant.config_entries import SOURCE_USER from homeassistant.config_entries import SOURCE_USER
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.util import dt as dt_util
from . import ENTRY_CONFIG from . import ENTRY_CONFIG
@ -32,9 +28,7 @@ async def no_sleep() -> AsyncGenerator[None]:
@pytest.fixture @pytest.fixture
async def load_int( async def load_int(hass: HomeAssistant, get_client: NordPoolClient) -> MockConfigEntry:
hass: HomeAssistant, get_data: DeliveryPeriodData
) -> MockConfigEntry:
"""Set up the Nord Pool integration in Home Assistant.""" """Set up the Nord Pool integration in Home Assistant."""
config_entry = MockConfigEntry( config_entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
@ -44,40 +38,83 @@ async def load_int(
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
with ( await hass.config_entries.async_setup(config_entry.entry_id)
patch(
"homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period",
return_value=get_data,
),
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
return config_entry return config_entry
@pytest.fixture(name="get_data") @pytest.fixture(name="get_client")
async def get_data_from_library( async def get_data_from_library(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, load_json: dict[str, Any] hass: HomeAssistant,
) -> DeliveryPeriodData: aioclient_mock: AiohttpClientMocker,
load_json: list[dict[str, Any]],
) -> AsyncGenerator[NordPoolClient]:
"""Retrieve data from Nord Pool library.""" """Retrieve data from Nord Pool library."""
aioclient_mock.request(
"GET",
url=API + "/DayAheadPrices",
params={
"date": "2024-11-05",
"market": "DayAhead",
"deliveryArea": "SE3,SE4",
"currency": "SEK",
},
json=load_json[0],
)
aioclient_mock.request(
"GET",
url=API + "/DayAheadPrices",
params={
"date": "2024-11-05",
"market": "DayAhead",
"deliveryArea": "SE3",
"currency": "EUR",
},
json=load_json[0],
)
aioclient_mock.request(
"GET",
url=API + "/DayAheadPrices",
params={
"date": "2024-11-04",
"market": "DayAhead",
"deliveryArea": "SE3,SE4",
"currency": "SEK",
},
json=load_json[1],
)
aioclient_mock.request(
"GET",
url=API + "/DayAheadPrices",
params={
"date": "2024-11-06",
"market": "DayAhead",
"deliveryArea": "SE3,SE4",
"currency": "SEK",
},
json=load_json[2],
)
client = NordPoolClient(aioclient_mock.create_session(hass.loop)) client = NordPoolClient(aioclient_mock.create_session(hass.loop))
with patch("pynordpool.NordPoolClient._get", return_value=load_json): yield client
output = await client.async_get_delivery_period(
datetime(2024, 11, 5, 13, tzinfo=dt_util.UTC), Currency.SEK, ["SE3", "SE4"]
)
await client._session.close() await client._session.close()
return output
@pytest.fixture(name="load_json") @pytest.fixture(name="load_json")
def load_json_from_fixture(load_data: str) -> dict[str, Any]: def load_json_from_fixture(load_data: list[str, str, str]) -> list[dict[str, Any]]:
"""Load fixture with json data and return.""" """Load fixture with json data and return."""
return json.loads(load_data) return [
json.loads(load_data[0]),
json.loads(load_data[1]),
json.loads(load_data[2]),
]
@pytest.fixture(name="load_data", scope="package") @pytest.fixture(name="load_data", scope="package")
def load_data_from_fixture() -> str: def load_data_from_fixture() -> list[str, str, str]:
"""Load fixture with fixture data and return.""" """Load fixture with fixture data and return."""
return load_fixture("delivery_period.json", DOMAIN) return [
load_fixture("delivery_period_today.json", DOMAIN),
load_fixture("delivery_period_yesterday.json", DOMAIN),
load_fixture("delivery_period_tomorrow.json", DOMAIN),
]

View File

@ -0,0 +1,272 @@
{
"deliveryDateCET": "2024-11-06",
"version": 3,
"updatedAt": "2024-11-05T12:12:51.9853434Z",
"deliveryAreas": ["SE3", "SE4"],
"market": "DayAhead",
"multiAreaEntries": [
{
"deliveryStart": "2024-11-05T23:00:00Z",
"deliveryEnd": "2024-11-06T00:00:00Z",
"entryPerArea": {
"SE3": 126.66,
"SE4": 275.6
}
},
{
"deliveryStart": "2024-11-06T00:00:00Z",
"deliveryEnd": "2024-11-06T01:00:00Z",
"entryPerArea": {
"SE3": 74.06,
"SE4": 157.34
}
},
{
"deliveryStart": "2024-11-06T01:00:00Z",
"deliveryEnd": "2024-11-06T02:00:00Z",
"entryPerArea": {
"SE3": 78.38,
"SE4": 165.62
}
},
{
"deliveryStart": "2024-11-06T02:00:00Z",
"deliveryEnd": "2024-11-06T03:00:00Z",
"entryPerArea": {
"SE3": 92.37,
"SE4": 196.17
}
},
{
"deliveryStart": "2024-11-06T03:00:00Z",
"deliveryEnd": "2024-11-06T04:00:00Z",
"entryPerArea": {
"SE3": 99.14,
"SE4": 190.58
}
},
{
"deliveryStart": "2024-11-06T04:00:00Z",
"deliveryEnd": "2024-11-06T05:00:00Z",
"entryPerArea": {
"SE3": 447.51,
"SE4": 932.93
}
},
{
"deliveryStart": "2024-11-06T05:00:00Z",
"deliveryEnd": "2024-11-06T06:00:00Z",
"entryPerArea": {
"SE3": 641.47,
"SE4": 1284.69
}
},
{
"deliveryStart": "2024-11-06T06:00:00Z",
"deliveryEnd": "2024-11-06T07:00:00Z",
"entryPerArea": {
"SE3": 1820.5,
"SE4": 2449.96
}
},
{
"deliveryStart": "2024-11-06T07:00:00Z",
"deliveryEnd": "2024-11-06T08:00:00Z",
"entryPerArea": {
"SE3": 1723.0,
"SE4": 2244.22
}
},
{
"deliveryStart": "2024-11-06T08:00:00Z",
"deliveryEnd": "2024-11-06T09:00:00Z",
"entryPerArea": {
"SE3": 1298.57,
"SE4": 1643.45
}
},
{
"deliveryStart": "2024-11-06T09:00:00Z",
"deliveryEnd": "2024-11-06T10:00:00Z",
"entryPerArea": {
"SE3": 1099.25,
"SE4": 1507.23
}
},
{
"deliveryStart": "2024-11-06T10:00:00Z",
"deliveryEnd": "2024-11-06T11:00:00Z",
"entryPerArea": {
"SE3": 903.31,
"SE4": 1362.84
}
},
{
"deliveryStart": "2024-11-06T11:00:00Z",
"deliveryEnd": "2024-11-06T12:00:00Z",
"entryPerArea": {
"SE3": 959.99,
"SE4": 1376.13
}
},
{
"deliveryStart": "2024-11-06T12:00:00Z",
"deliveryEnd": "2024-11-06T13:00:00Z",
"entryPerArea": {
"SE3": 1186.61,
"SE4": 1449.96
}
},
{
"deliveryStart": "2024-11-06T13:00:00Z",
"deliveryEnd": "2024-11-06T14:00:00Z",
"entryPerArea": {
"SE3": 1307.67,
"SE4": 1608.35
}
},
{
"deliveryStart": "2024-11-06T14:00:00Z",
"deliveryEnd": "2024-11-06T15:00:00Z",
"entryPerArea": {
"SE3": 1385.46,
"SE4": 2110.8
}
},
{
"deliveryStart": "2024-11-06T15:00:00Z",
"deliveryEnd": "2024-11-06T16:00:00Z",
"entryPerArea": {
"SE3": 1366.8,
"SE4": 3031.25
}
},
{
"deliveryStart": "2024-11-06T16:00:00Z",
"deliveryEnd": "2024-11-06T17:00:00Z",
"entryPerArea": {
"SE3": 2366.57,
"SE4": 5511.77
}
},
{
"deliveryStart": "2024-11-06T17:00:00Z",
"deliveryEnd": "2024-11-06T18:00:00Z",
"entryPerArea": {
"SE3": 1481.92,
"SE4": 3351.64
}
},
{
"deliveryStart": "2024-11-06T18:00:00Z",
"deliveryEnd": "2024-11-06T19:00:00Z",
"entryPerArea": {
"SE3": 1082.69,
"SE4": 2484.95
}
},
{
"deliveryStart": "2024-11-06T19:00:00Z",
"deliveryEnd": "2024-11-06T20:00:00Z",
"entryPerArea": {
"SE3": 716.82,
"SE4": 1624.33
}
},
{
"deliveryStart": "2024-11-06T20:00:00Z",
"deliveryEnd": "2024-11-06T21:00:00Z",
"entryPerArea": {
"SE3": 583.16,
"SE4": 1306.27
}
},
{
"deliveryStart": "2024-11-06T21:00:00Z",
"deliveryEnd": "2024-11-06T22:00:00Z",
"entryPerArea": {
"SE3": 523.09,
"SE4": 1142.99
}
},
{
"deliveryStart": "2024-11-06T22:00:00Z",
"deliveryEnd": "2024-11-06T23:00:00Z",
"entryPerArea": {
"SE3": 250.64,
"SE4": 539.42
}
}
],
"blockPriceAggregates": [
{
"blockName": "Off-peak 1",
"deliveryStart": "2024-11-05T23:00:00Z",
"deliveryEnd": "2024-11-06T07:00:00Z",
"averagePricePerArea": {
"SE3": {
"average": 422.51,
"min": 74.06,
"max": 1820.5
},
"SE4": {
"average": 706.61,
"min": 157.34,
"max": 2449.96
}
}
},
{
"blockName": "Peak",
"deliveryStart": "2024-11-06T07:00:00Z",
"deliveryEnd": "2024-11-06T19:00:00Z",
"averagePricePerArea": {
"SE3": {
"average": 1346.82,
"min": 903.31,
"max": 2366.57
},
"SE4": {
"average": 2306.88,
"min": 1362.84,
"max": 5511.77
}
}
},
{
"blockName": "Off-peak 2",
"deliveryStart": "2024-11-06T19:00:00Z",
"deliveryEnd": "2024-11-06T23:00:00Z",
"averagePricePerArea": {
"SE3": {
"average": 518.43,
"min": 250.64,
"max": 716.82
},
"SE4": {
"average": 1153.25,
"min": 539.42,
"max": 1624.33
}
}
}
],
"currency": "SEK",
"exchangeRate": 11.66314,
"areaStates": [
{
"state": "Final",
"areas": ["SE3", "SE4"]
}
],
"areaAverages": [
{
"areaCode": "SE3",
"price": 900.65
},
{
"areaCode": "SE4",
"price": 1581.19
}
]
}

View File

@ -0,0 +1,272 @@
{
"deliveryDateCET": "2024-11-04",
"version": 3,
"updatedAt": "2024-11-04T08:09:11.1931991Z",
"deliveryAreas": ["SE3", "SE4"],
"market": "DayAhead",
"multiAreaEntries": [
{
"deliveryStart": "2024-11-03T23:00:00Z",
"deliveryEnd": "2024-11-04T00:00:00Z",
"entryPerArea": {
"SE3": 66.13,
"SE4": 78.59
}
},
{
"deliveryStart": "2024-11-04T00:00:00Z",
"deliveryEnd": "2024-11-04T01:00:00Z",
"entryPerArea": {
"SE3": 72.54,
"SE4": 86.51
}
},
{
"deliveryStart": "2024-11-04T01:00:00Z",
"deliveryEnd": "2024-11-04T02:00:00Z",
"entryPerArea": {
"SE3": 73.12,
"SE4": 84.88
}
},
{
"deliveryStart": "2024-11-04T02:00:00Z",
"deliveryEnd": "2024-11-04T03:00:00Z",
"entryPerArea": {
"SE3": 171.97,
"SE4": 217.26
}
},
{
"deliveryStart": "2024-11-04T03:00:00Z",
"deliveryEnd": "2024-11-04T04:00:00Z",
"entryPerArea": {
"SE3": 181.05,
"SE4": 227.74
}
},
{
"deliveryStart": "2024-11-04T04:00:00Z",
"deliveryEnd": "2024-11-04T05:00:00Z",
"entryPerArea": {
"SE3": 360.71,
"SE4": 414.61
}
},
{
"deliveryStart": "2024-11-04T05:00:00Z",
"deliveryEnd": "2024-11-04T06:00:00Z",
"entryPerArea": {
"SE3": 917.83,
"SE4": 1439.33
}
},
{
"deliveryStart": "2024-11-04T06:00:00Z",
"deliveryEnd": "2024-11-04T07:00:00Z",
"entryPerArea": {
"SE3": 1426.17,
"SE4": 1695.95
}
},
{
"deliveryStart": "2024-11-04T07:00:00Z",
"deliveryEnd": "2024-11-04T08:00:00Z",
"entryPerArea": {
"SE3": 1350.96,
"SE4": 1605.13
}
},
{
"deliveryStart": "2024-11-04T08:00:00Z",
"deliveryEnd": "2024-11-04T09:00:00Z",
"entryPerArea": {
"SE3": 1195.06,
"SE4": 1393.46
}
},
{
"deliveryStart": "2024-11-04T09:00:00Z",
"deliveryEnd": "2024-11-04T10:00:00Z",
"entryPerArea": {
"SE3": 992.35,
"SE4": 1126.71
}
},
{
"deliveryStart": "2024-11-04T10:00:00Z",
"deliveryEnd": "2024-11-04T11:00:00Z",
"entryPerArea": {
"SE3": 976.63,
"SE4": 1107.97
}
},
{
"deliveryStart": "2024-11-04T11:00:00Z",
"deliveryEnd": "2024-11-04T12:00:00Z",
"entryPerArea": {
"SE3": 952.76,
"SE4": 1085.73
}
},
{
"deliveryStart": "2024-11-04T12:00:00Z",
"deliveryEnd": "2024-11-04T13:00:00Z",
"entryPerArea": {
"SE3": 1029.37,
"SE4": 1177.71
}
},
{
"deliveryStart": "2024-11-04T13:00:00Z",
"deliveryEnd": "2024-11-04T14:00:00Z",
"entryPerArea": {
"SE3": 1043.35,
"SE4": 1194.59
}
},
{
"deliveryStart": "2024-11-04T14:00:00Z",
"deliveryEnd": "2024-11-04T15:00:00Z",
"entryPerArea": {
"SE3": 1359.57,
"SE4": 1561.12
}
},
{
"deliveryStart": "2024-11-04T15:00:00Z",
"deliveryEnd": "2024-11-04T16:00:00Z",
"entryPerArea": {
"SE3": 1848.35,
"SE4": 2145.84
}
},
{
"deliveryStart": "2024-11-04T16:00:00Z",
"deliveryEnd": "2024-11-04T17:00:00Z",
"entryPerArea": {
"SE3": 2812.53,
"SE4": 3313.53
}
},
{
"deliveryStart": "2024-11-04T17:00:00Z",
"deliveryEnd": "2024-11-04T18:00:00Z",
"entryPerArea": {
"SE3": 2351.69,
"SE4": 2751.87
}
},
{
"deliveryStart": "2024-11-04T18:00:00Z",
"deliveryEnd": "2024-11-04T19:00:00Z",
"entryPerArea": {
"SE3": 1553.08,
"SE4": 1842.77
}
},
{
"deliveryStart": "2024-11-04T19:00:00Z",
"deliveryEnd": "2024-11-04T20:00:00Z",
"entryPerArea": {
"SE3": 1165.02,
"SE4": 1398.35
}
},
{
"deliveryStart": "2024-11-04T20:00:00Z",
"deliveryEnd": "2024-11-04T21:00:00Z",
"entryPerArea": {
"SE3": 1007.48,
"SE4": 1172.35
}
},
{
"deliveryStart": "2024-11-04T21:00:00Z",
"deliveryEnd": "2024-11-04T22:00:00Z",
"entryPerArea": {
"SE3": 792.09,
"SE4": 920.28
}
},
{
"deliveryStart": "2024-11-04T22:00:00Z",
"deliveryEnd": "2024-11-04T23:00:00Z",
"entryPerArea": {
"SE3": 465.38,
"SE4": 528.83
}
}
],
"blockPriceAggregates": [
{
"blockName": "Off-peak 1",
"deliveryStart": "2024-11-03T23:00:00Z",
"deliveryEnd": "2024-11-04T07:00:00Z",
"averagePricePerArea": {
"SE3": {
"average": 408.69,
"min": 66.13,
"max": 1426.17
},
"SE4": {
"average": 530.61,
"min": 78.59,
"max": 1695.95
}
}
},
{
"blockName": "Peak",
"deliveryStart": "2024-11-04T07:00:00Z",
"deliveryEnd": "2024-11-04T19:00:00Z",
"averagePricePerArea": {
"SE3": {
"average": 1455.48,
"min": 952.76,
"max": 2812.53
},
"SE4": {
"average": 1692.2,
"min": 1085.73,
"max": 3313.53
}
}
},
{
"blockName": "Off-peak 2",
"deliveryStart": "2024-11-04T19:00:00Z",
"deliveryEnd": "2024-11-04T23:00:00Z",
"averagePricePerArea": {
"SE3": {
"average": 857.49,
"min": 465.38,
"max": 1165.02
},
"SE4": {
"average": 1004.95,
"min": 528.83,
"max": 1398.35
}
}
}
],
"currency": "SEK",
"exchangeRate": 11.64318,
"areaStates": [
{
"state": "Final",
"areas": ["SE3", "SE4"]
}
],
"areaAverages": [
{
"areaCode": "SE3",
"price": 1006.88
},
{
"areaCode": "SE4",
"price": 1190.46
}
]
}

File diff suppressed because it is too large Load Diff

View File

@ -2,10 +2,11 @@
from __future__ import annotations from __future__ import annotations
from typing import Any
from unittest.mock import patch from unittest.mock import patch
from pynordpool import ( from pynordpool import (
DeliveryPeriodData, NordPoolClient,
NordPoolConnectionError, NordPoolConnectionError,
NordPoolEmptyResponseError, NordPoolEmptyResponseError,
NordPoolError, NordPoolError,
@ -22,10 +23,11 @@ from homeassistant.data_entry_flow import FlowResultType
from . import ENTRY_CONFIG from . import ENTRY_CONFIG
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.test_util.aiohttp import AiohttpClientMocker
@pytest.mark.freeze_time("2024-11-05T18:00:00+00:00") @pytest.mark.freeze_time("2024-11-05T18:00:00+00:00")
async def test_form(hass: HomeAssistant, get_data: DeliveryPeriodData) -> None: async def test_form(hass: HomeAssistant, get_client: NordPoolClient) -> None:
"""Test we get the form.""" """Test we get the form."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -34,17 +36,11 @@ async def test_form(hass: HomeAssistant, get_data: DeliveryPeriodData) -> None:
assert result["step_id"] == "user" assert result["step_id"] == "user"
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
with ( result = await hass.config_entries.flow.async_configure(
patch( result["flow_id"],
"homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period", ENTRY_CONFIG,
return_value=get_data, )
), await hass.async_block_till_done()
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
ENTRY_CONFIG,
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["version"] == 1 assert result["version"] == 1
@ -54,7 +50,7 @@ async def test_form(hass: HomeAssistant, get_data: DeliveryPeriodData) -> None:
@pytest.mark.freeze_time("2024-11-05T18:00:00+00:00") @pytest.mark.freeze_time("2024-11-05T18:00:00+00:00")
async def test_single_config_entry( async def test_single_config_entry(
hass: HomeAssistant, load_int: None, get_data: DeliveryPeriodData hass: HomeAssistant, load_int: None, get_client: NordPoolClient
) -> None: ) -> None:
"""Test abort for single config entry.""" """Test abort for single config entry."""
@ -77,7 +73,7 @@ async def test_single_config_entry(
) )
async def test_cannot_connect( async def test_cannot_connect(
hass: HomeAssistant, hass: HomeAssistant,
get_data: DeliveryPeriodData, get_client: NordPoolClient,
error_message: Exception, error_message: Exception,
p_error: str, p_error: str,
) -> None: ) -> None:
@ -101,14 +97,10 @@ async def test_cannot_connect(
assert result["errors"] == {"base": p_error} assert result["errors"] == {"base": p_error}
with patch( result = await hass.config_entries.flow.async_configure(
"homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period", result["flow_id"],
return_value=get_data, user_input=ENTRY_CONFIG,
): )
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=ENTRY_CONFIG,
)
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "Nord Pool" assert result["title"] == "Nord Pool"
@ -119,25 +111,18 @@ async def test_cannot_connect(
async def test_reconfigure( async def test_reconfigure(
hass: HomeAssistant, hass: HomeAssistant,
load_int: MockConfigEntry, load_int: MockConfigEntry,
get_data: DeliveryPeriodData,
) -> None: ) -> None:
"""Test reconfiguration.""" """Test reconfiguration."""
result = await load_int.start_reconfigure_flow(hass) result = await load_int.start_reconfigure_flow(hass)
with ( result = await hass.config_entries.flow.async_configure(
patch( result["flow_id"],
"homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period", {
return_value=get_data, CONF_AREAS: ["SE3"],
), CONF_CURRENCY: "EUR",
): },
result = await hass.config_entries.flow.async_configure( )
result["flow_id"],
{
CONF_AREAS: ["SE3"],
CONF_CURRENCY: "EUR",
},
)
assert result["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful" assert result["reason"] == "reconfigure_successful"
@ -162,7 +147,8 @@ async def test_reconfigure(
async def test_reconfigure_cannot_connect( async def test_reconfigure_cannot_connect(
hass: HomeAssistant, hass: HomeAssistant,
load_int: MockConfigEntry, load_int: MockConfigEntry,
get_data: DeliveryPeriodData, aioclient_mock: AiohttpClientMocker,
load_json: list[dict[str, Any]],
error_message: Exception, error_message: Exception,
p_error: str, p_error: str,
) -> None: ) -> None:
@ -184,17 +170,13 @@ async def test_reconfigure_cannot_connect(
assert result["errors"] == {"base": p_error} assert result["errors"] == {"base": p_error}
with patch( result = await hass.config_entries.flow.async_configure(
"homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period", result["flow_id"],
return_value=get_data, user_input={
): CONF_AREAS: ["SE3"],
result = await hass.config_entries.flow.async_configure( CONF_CURRENCY: "EUR",
result["flow_id"], },
user_input={ )
CONF_AREAS: ["SE3"],
CONF_CURRENCY: "EUR",
},
)
assert result["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful" assert result["reason"] == "reconfigure_successful"

View File

@ -7,8 +7,8 @@ from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
from pynordpool import ( from pynordpool import (
DeliveryPeriodData,
NordPoolAuthenticationError, NordPoolAuthenticationError,
NordPoolClient,
NordPoolEmptyResponseError, NordPoolEmptyResponseError,
NordPoolError, NordPoolError,
NordPoolResponseError, NordPoolResponseError,
@ -28,7 +28,7 @@ from tests.common import MockConfigEntry, async_fire_time_changed
@pytest.mark.freeze_time("2024-11-05T10:00:00+00:00") @pytest.mark.freeze_time("2024-11-05T10:00:00+00:00")
async def test_coordinator( async def test_coordinator(
hass: HomeAssistant, hass: HomeAssistant,
get_data: DeliveryPeriodData, get_client: NordPoolClient,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
) -> None: ) -> None:
@ -41,30 +41,31 @@ async def test_coordinator(
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("sensor.nord_pool_se3_current_price")
assert state.state == "0.92737"
with ( with (
patch( patch(
"homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period", "homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period",
side_effect=NordPoolError("error"),
) as mock_data, ) as mock_data,
): ):
mock_data.return_value = get_data
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
mock_data.assert_called_once()
state = hass.states.get("sensor.nord_pool_se3_current_price")
assert state.state == "0.92737"
mock_data.reset_mock()
mock_data.side_effect = NordPoolError("error")
freezer.tick(timedelta(hours=1)) freezer.tick(timedelta(hours=1))
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True) await hass.async_block_till_done(wait_background_tasks=True)
assert mock_data.call_count == 4 assert mock_data.call_count == 4
state = hass.states.get("sensor.nord_pool_se3_current_price") state = hass.states.get("sensor.nord_pool_se3_current_price")
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE
mock_data.reset_mock()
with (
patch(
"homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period",
side_effect=NordPoolAuthenticationError("Authentication error"),
) as mock_data,
):
assert "Authentication error" not in caplog.text assert "Authentication error" not in caplog.text
mock_data.side_effect = NordPoolAuthenticationError("Authentication error")
freezer.tick(timedelta(hours=1)) freezer.tick(timedelta(hours=1))
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True) await hass.async_block_till_done(wait_background_tasks=True)
@ -72,10 +73,14 @@ async def test_coordinator(
state = hass.states.get("sensor.nord_pool_se3_current_price") state = hass.states.get("sensor.nord_pool_se3_current_price")
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE
assert "Authentication error" in caplog.text assert "Authentication error" in caplog.text
mock_data.reset_mock()
with (
patch(
"homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period",
side_effect=NordPoolEmptyResponseError("Empty response"),
) as mock_data,
):
assert "Empty response" not in caplog.text assert "Empty response" not in caplog.text
mock_data.side_effect = NordPoolEmptyResponseError("Empty response")
freezer.tick(timedelta(hours=1)) freezer.tick(timedelta(hours=1))
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True) await hass.async_block_till_done(wait_background_tasks=True)
@ -83,10 +88,14 @@ async def test_coordinator(
state = hass.states.get("sensor.nord_pool_se3_current_price") state = hass.states.get("sensor.nord_pool_se3_current_price")
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE
assert "Empty response" in caplog.text assert "Empty response" in caplog.text
mock_data.reset_mock()
with (
patch(
"homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period",
side_effect=NordPoolResponseError("Response error"),
) as mock_data,
):
assert "Response error" not in caplog.text assert "Response error" not in caplog.text
mock_data.side_effect = NordPoolResponseError("Response error")
freezer.tick(timedelta(hours=1)) freezer.tick(timedelta(hours=1))
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True) await hass.async_block_till_done(wait_background_tasks=True)
@ -94,13 +103,9 @@ async def test_coordinator(
state = hass.states.get("sensor.nord_pool_se3_current_price") state = hass.states.get("sensor.nord_pool_se3_current_price")
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE
assert "Response error" in caplog.text assert "Response error" in caplog.text
mock_data.reset_mock()
mock_data.return_value = get_data freezer.tick(timedelta(hours=1))
mock_data.side_effect = None async_fire_time_changed(hass)
freezer.tick(timedelta(hours=1)) await hass.async_block_till_done()
async_fire_time_changed(hass) state = hass.states.get("sensor.nord_pool_se3_current_price")
await hass.async_block_till_done() assert state.state == "1.81645"
mock_data.assert_called_once()
state = hass.states.get("sensor.nord_pool_se3_current_price")
assert state.state == "1.81645"

View File

@ -2,19 +2,21 @@
from __future__ import annotations from __future__ import annotations
import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.components.diagnostics import get_diagnostics_for_config_entry
from tests.typing import ClientSessionGenerator from tests.typing import ClientSessionGenerator
@pytest.mark.freeze_time("2024-11-05T10:00:00+00:00")
async def test_diagnostics( async def test_diagnostics(
hass: HomeAssistant, hass: HomeAssistant,
hass_client: ClientSessionGenerator, hass_client: ClientSessionGenerator,
load_int: ConfigEntry, load_int: MockConfigEntry,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
) -> None: ) -> None:
"""Test generating diagnostics for a config entry.""" """Test generating diagnostics for a config entry."""

View File

@ -5,7 +5,7 @@ from __future__ import annotations
from unittest.mock import patch from unittest.mock import patch
from pynordpool import ( from pynordpool import (
DeliveryPeriodData, NordPoolClient,
NordPoolConnectionError, NordPoolConnectionError,
NordPoolEmptyResponseError, NordPoolEmptyResponseError,
NordPoolError, NordPoolError,
@ -22,7 +22,8 @@ from . import ENTRY_CONFIG
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
async def test_unload_entry(hass: HomeAssistant, get_data: DeliveryPeriodData) -> None: @pytest.mark.freeze_time("2024-11-05T10:00:00+00:00")
async def test_unload_entry(hass: HomeAssistant, get_client: NordPoolClient) -> None:
"""Test load and unload an entry.""" """Test load and unload an entry."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
@ -31,13 +32,7 @@ async def test_unload_entry(hass: HomeAssistant, get_data: DeliveryPeriodData) -
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
with ( await hass.config_entries.async_setup(entry.entry_id)
patch(
"homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period",
return_value=get_data,
),
):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done(wait_background_tasks=True) await hass.async_block_till_done(wait_background_tasks=True)
assert entry.state is ConfigEntryState.LOADED assert entry.state is ConfigEntryState.LOADED
@ -56,7 +51,7 @@ async def test_unload_entry(hass: HomeAssistant, get_data: DeliveryPeriodData) -
], ],
) )
async def test_initial_startup_fails( async def test_initial_startup_fails(
hass: HomeAssistant, get_data: DeliveryPeriodData, error: Exception hass: HomeAssistant, get_client: NordPoolClient, error: Exception
) -> None: ) -> None:
"""Test load and unload an entry.""" """Test load and unload an entry."""
entry = MockConfigEntry( entry = MockConfigEntry(

View File

@ -6,7 +6,6 @@ import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_UNKNOWN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
@ -38,12 +37,12 @@ async def test_sensor_no_next_price(hass: HomeAssistant, load_int: ConfigEntry)
assert current_price is not None assert current_price is not None
assert last_price is not None assert last_price is not None
assert next_price is not None assert next_price is not None
assert current_price.state == "0.28914" assert current_price.state == "0.12666" # SE3 2024-11-05T23:00:00Z
assert last_price.state == "0.28914" assert last_price.state == "0.28914" # SE3 2024-11-05T22:00:00Z
assert next_price.state == STATE_UNKNOWN assert next_price.state == "0.07406" # SE3 2024-11-06T00:00:00Z"
@pytest.mark.freeze_time("2024-11-05T00:00:00+01:00") @pytest.mark.freeze_time("2024-11-06T00:00:00+01:00")
@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_sensor_no_previous_price( async def test_sensor_no_previous_price(
hass: HomeAssistant, load_int: ConfigEntry hass: HomeAssistant, load_int: ConfigEntry
@ -57,6 +56,6 @@ async def test_sensor_no_previous_price(
assert current_price is not None assert current_price is not None
assert last_price is not None assert last_price is not None
assert next_price is not None assert next_price is not None
assert current_price.state == "0.25073" assert current_price.state == "0.12666" # SE3 2024-11-05T23:00:00Z
assert last_price.state == STATE_UNKNOWN assert last_price.state == "0.28914" # SE3 2024-11-05T22:00:00Z
assert next_price.state == "0.07636" assert next_price.state == "0.07406" # SE3 2024-11-06T00:00:00Z

View File

@ -3,7 +3,6 @@
from unittest.mock import patch from unittest.mock import patch
from pynordpool import ( from pynordpool import (
DeliveryPeriodData,
NordPoolAuthenticationError, NordPoolAuthenticationError,
NordPoolEmptyResponseError, NordPoolEmptyResponseError,
NordPoolError, NordPoolError,
@ -28,7 +27,7 @@ TEST_SERVICE_DATA = {
ATTR_CONFIG_ENTRY: "to_replace", ATTR_CONFIG_ENTRY: "to_replace",
ATTR_DATE: "2024-11-05", ATTR_DATE: "2024-11-05",
ATTR_AREAS: "SE3", ATTR_AREAS: "SE3",
ATTR_CURRENCY: "SEK", ATTR_CURRENCY: "EUR",
} }
TEST_SERVICE_DATA_USE_DEFAULTS = { TEST_SERVICE_DATA_USE_DEFAULTS = {
ATTR_CONFIG_ENTRY: "to_replace", ATTR_CONFIG_ENTRY: "to_replace",
@ -40,45 +39,32 @@ TEST_SERVICE_DATA_USE_DEFAULTS = {
async def test_service_call( async def test_service_call(
hass: HomeAssistant, hass: HomeAssistant,
load_int: MockConfigEntry, load_int: MockConfigEntry,
get_data: DeliveryPeriodData,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
) -> None: ) -> None:
"""Test get_prices_for_date service call.""" """Test get_prices_for_date service call."""
with ( service_data = TEST_SERVICE_DATA.copy()
patch( service_data[ATTR_CONFIG_ENTRY] = load_int.entry_id
"homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period", response = await hass.services.async_call(
return_value=get_data, DOMAIN,
), SERVICE_GET_PRICES_FOR_DATE,
): service_data,
service_data = TEST_SERVICE_DATA.copy() blocking=True,
service_data[ATTR_CONFIG_ENTRY] = load_int.entry_id return_response=True,
response = await hass.services.async_call( )
DOMAIN,
SERVICE_GET_PRICES_FOR_DATE,
service_data,
blocking=True,
return_response=True,
)
assert response == snapshot assert response == snapshot
price_value = response["SE3"][0]["price"] price_value = response["SE3"][0]["price"]
with ( service_data = TEST_SERVICE_DATA_USE_DEFAULTS.copy()
patch( service_data[ATTR_CONFIG_ENTRY] = load_int.entry_id
"homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period", response = await hass.services.async_call(
return_value=get_data, DOMAIN,
), SERVICE_GET_PRICES_FOR_DATE,
): service_data,
service_data = TEST_SERVICE_DATA_USE_DEFAULTS.copy() blocking=True,
service_data[ATTR_CONFIG_ENTRY] = load_int.entry_id return_response=True,
response = await hass.services.async_call( )
DOMAIN,
SERVICE_GET_PRICES_FOR_DATE,
service_data,
blocking=True,
return_response=True,
)
assert "SE3" in response assert "SE3" in response
assert response["SE3"][0]["price"] == price_value assert response["SE3"][0]["price"] == price_value
@ -124,17 +110,10 @@ async def test_service_call_failures(
async def test_service_call_config_entry_bad_state( async def test_service_call_config_entry_bad_state(
hass: HomeAssistant, hass: HomeAssistant,
load_int: MockConfigEntry, load_int: MockConfigEntry,
get_data: DeliveryPeriodData,
) -> None: ) -> None:
"""Test get_prices_for_date service call when config entry bad state.""" """Test get_prices_for_date service call when config entry bad state."""
with ( with pytest.raises(ServiceValidationError) as err:
patch(
"homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period",
return_value=get_data,
),
pytest.raises(ServiceValidationError) as err,
):
await hass.services.async_call( await hass.services.async_call(
DOMAIN, DOMAIN,
SERVICE_GET_PRICES_FOR_DATE, SERVICE_GET_PRICES_FOR_DATE,
@ -149,13 +128,7 @@ async def test_service_call_config_entry_bad_state(
await hass.config_entries.async_unload(load_int.entry_id) await hass.config_entries.async_unload(load_int.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
with ( with pytest.raises(ServiceValidationError) as err:
patch(
"homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period",
return_value=get_data,
),
pytest.raises(ServiceValidationError) as err,
):
await hass.services.async_call( await hass.services.async_call(
DOMAIN, DOMAIN,
SERVICE_GET_PRICES_FOR_DATE, SERVICE_GET_PRICES_FOR_DATE,