Fix Nord Pool empty response (#134033)

* Fix Nord Pool empty response

* Mods

* reset validate prices
This commit is contained in:
G Johansson 2024-12-28 21:38:04 +01:00 committed by Paulus Schoutsen
parent 1957ab1ccf
commit c11bdcc949
7 changed files with 155 additions and 24 deletions

View File

@ -2,7 +2,6 @@
from __future__ import annotations
import asyncio
from collections.abc import Callable
from datetime import datetime, timedelta
from typing import TYPE_CHECKING
@ -73,7 +72,7 @@ class NordPoolDataUpdateCoordinator(DataUpdateCoordinator[DeliveryPeriodsData]):
self.hass, self.fetch_data, self.get_next_interval(dt_util.utcnow())
)
data = await self.api_call()
if data:
if data and data.entries:
self.async_set_updated_data(data)
async def api_call(self, retry: int = 3) -> DeliveryPeriodsData | None:
@ -90,18 +89,20 @@ class NordPoolDataUpdateCoordinator(DataUpdateCoordinator[DeliveryPeriodsData]):
self.config_entry.data[CONF_AREAS],
)
except (
NordPoolEmptyResponseError,
NordPoolResponseError,
NordPoolError,
) as error:
LOGGER.debug("Connection error: %s", error)
if retry > 0:
next_run = (4 - retry) * 15
LOGGER.debug("Wait %d seconds for next try", next_run)
await asyncio.sleep(next_run)
return await self.api_call(retry - 1)
self.async_set_update_error(error)
if data:
current_day = dt_util.utcnow().strftime("%Y-%m-%d")
for entry in data.entries:
if entry.requested_date == current_day:
LOGGER.debug("Data for current day found")
return data
self.async_set_update_error(NordPoolEmptyResponseError("No current day data"))
return data
def merge_price_entries(self) -> list[DeliveryPeriodEntry]:

View File

@ -8,6 +8,6 @@
"iot_class": "cloud_polling",
"loggers": ["pynordpool"],
"quality_scale": "platinum",
"requirements": ["pynordpool==0.2.3"],
"requirements": ["pynordpool==0.2.4"],
"single_config_entry": true
}

View File

@ -2118,7 +2118,7 @@ pynetio==0.1.9.1
pynobo==1.8.1
# homeassistant.components.nordpool
pynordpool==0.2.3
pynordpool==0.2.4
# homeassistant.components.nuki
pynuki==1.6.3

View File

@ -1720,7 +1720,7 @@ pynetgear==0.10.10
pynobo==1.8.1
# homeassistant.components.nordpool
pynordpool==0.2.3
pynordpool==0.2.4
# homeassistant.components.nuki
pynuki==1.6.3

View File

@ -5,7 +5,6 @@ from __future__ import annotations
from collections.abc import AsyncGenerator
import json
from typing import Any
from unittest.mock import patch
from pynordpool import API, NordPoolClient
import pytest
@ -20,13 +19,6 @@ from tests.common import MockConfigEntry, load_fixture
from tests.test_util.aiohttp import AiohttpClientMocker
@pytest.fixture(autouse=True)
async def no_sleep() -> AsyncGenerator[None]:
"""No sleeping."""
with patch("homeassistant.components.nordpool.coordinator.asyncio.sleep"):
yield
@pytest.fixture
async def load_int(hass: HomeAssistant, get_client: NordPoolClient) -> MockConfigEntry:
"""Set up the Nord Pool integration in Home Assistant."""

View File

@ -55,7 +55,7 @@ async def test_coordinator(
freezer.tick(timedelta(hours=1))
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
assert mock_data.call_count == 4
assert mock_data.call_count == 1
state = hass.states.get("sensor.nord_pool_se3_current_price")
assert state.state == STATE_UNAVAILABLE
@ -69,7 +69,7 @@ async def test_coordinator(
freezer.tick(timedelta(hours=1))
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
assert mock_data.call_count == 4
assert mock_data.call_count == 1
state = hass.states.get("sensor.nord_pool_se3_current_price")
assert state.state == STATE_UNAVAILABLE
assert "Authentication error" in caplog.text
@ -84,7 +84,8 @@ async def test_coordinator(
freezer.tick(timedelta(hours=1))
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
assert mock_data.call_count == 4
# Empty responses does not raise
assert mock_data.call_count == 3
state = hass.states.get("sensor.nord_pool_se3_current_price")
assert state.state == STATE_UNAVAILABLE
assert "Empty response" in caplog.text
@ -99,7 +100,7 @@ async def test_coordinator(
freezer.tick(timedelta(hours=1))
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
assert mock_data.call_count == 4
assert mock_data.call_count == 1
state = hass.states.get("sensor.nord_pool_se3_current_price")
assert state.state == STATE_UNAVAILABLE
assert "Response error" in caplog.text

View File

@ -2,14 +2,22 @@
from __future__ import annotations
from datetime import timedelta
from http import HTTPStatus
from typing import Any
from freezegun.api import FrozenDateTimeFactory
from pynordpool import API
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_UNKNOWN
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import snapshot_platform
from tests.common import async_fire_time_changed, snapshot_platform
from tests.test_util.aiohttp import AiohttpClientMocker
@pytest.mark.freeze_time("2024-11-05T18:00:00+00:00")
@ -59,3 +67,132 @@ async def test_sensor_no_previous_price(
assert current_price.state == "0.12666" # SE3 2024-11-05T23:00:00Z
assert last_price.state == "0.28914" # SE3 2024-11-05T22:00:00Z
assert next_price.state == "0.07406" # SE3 2024-11-06T00:00:00Z
@pytest.mark.freeze_time("2024-11-05T11:00:01+01:00")
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_sensor_empty_response(
hass: HomeAssistant,
load_int: ConfigEntry,
load_json: list[dict[str, Any]],
aioclient_mock: AiohttpClientMocker,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test the Nord Pool sensor with empty response."""
responses = list(load_json)
current_price = hass.states.get("sensor.nord_pool_se3_current_price")
last_price = hass.states.get("sensor.nord_pool_se3_previous_price")
next_price = hass.states.get("sensor.nord_pool_se3_next_price")
assert current_price is not None
assert last_price is not None
assert next_price is not None
assert current_price.state == "0.92737"
assert last_price.state == "1.03132"
assert next_price.state == "0.92505"
aioclient_mock.clear_requests()
aioclient_mock.request(
"GET",
url=API + "/DayAheadPrices",
params={
"date": "2024-11-04",
"market": "DayAhead",
"deliveryArea": "SE3,SE4",
"currency": "SEK",
},
json=responses[1],
)
aioclient_mock.request(
"GET",
url=API + "/DayAheadPrices",
params={
"date": "2024-11-05",
"market": "DayAhead",
"deliveryArea": "SE3,SE4",
"currency": "SEK",
},
json=responses[0],
)
# Future date without data should return 204
aioclient_mock.request(
"GET",
url=API + "/DayAheadPrices",
params={
"date": "2024-11-06",
"market": "DayAhead",
"deliveryArea": "SE3,SE4",
"currency": "SEK",
},
status=HTTPStatus.NO_CONTENT,
)
freezer.tick(timedelta(hours=1))
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
# All prices should be known as tomorrow is not loaded by sensors
current_price = hass.states.get("sensor.nord_pool_se3_current_price")
last_price = hass.states.get("sensor.nord_pool_se3_previous_price")
next_price = hass.states.get("sensor.nord_pool_se3_next_price")
assert current_price is not None
assert last_price is not None
assert next_price is not None
assert current_price.state == "0.92505"
assert last_price.state == "0.92737"
assert next_price.state == "0.94949"
aioclient_mock.clear_requests()
aioclient_mock.request(
"GET",
url=API + "/DayAheadPrices",
params={
"date": "2024-11-04",
"market": "DayAhead",
"deliveryArea": "SE3,SE4",
"currency": "SEK",
},
json=responses[1],
)
aioclient_mock.request(
"GET",
url=API + "/DayAheadPrices",
params={
"date": "2024-11-05",
"market": "DayAhead",
"deliveryArea": "SE3,SE4",
"currency": "SEK",
},
json=responses[0],
)
# Future date without data should return 204
aioclient_mock.request(
"GET",
url=API + "/DayAheadPrices",
params={
"date": "2024-11-06",
"market": "DayAhead",
"deliveryArea": "SE3,SE4",
"currency": "SEK",
},
status=HTTPStatus.NO_CONTENT,
)
freezer.move_to("2024-11-05T22:00:01+00:00")
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
# Current and last price should be known, next price should be unknown
# as api responds with empty data (204)
current_price = hass.states.get("sensor.nord_pool_se3_current_price")
last_price = hass.states.get("sensor.nord_pool_se3_previous_price")
next_price = hass.states.get("sensor.nord_pool_se3_next_price")
assert current_price is not None
assert last_price is not None
assert next_price is not None
assert current_price.state == "0.28914"
assert last_price.state == "0.5223"
assert next_price.state == STATE_UNKNOWN