mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-29 21:03:40 +00:00
fix(validation): enhance user data validation to require active subscription and price info.
Fixes #73
This commit is contained in:
parent
4b32568665
commit
f88d6738e6
2 changed files with 122 additions and 25 deletions
|
|
@ -218,21 +218,43 @@ class TibberPricesPriceDataManager:
|
||||||
self._log("warning", "User data validation failed: Home %s missing timezone", home_id)
|
self._log("warning", "User data validation failed: Home %s missing timezone", home_id)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Currency is critical - if home has subscription, must have currency
|
# Currency is REQUIRED - we cannot function without it
|
||||||
|
# The currency is nested in currentSubscription.priceInfo.current.currency
|
||||||
subscription = home.get("currentSubscription")
|
subscription = home.get("currentSubscription")
|
||||||
if subscription and subscription is not None:
|
if not subscription:
|
||||||
price_info = subscription.get("priceInfo")
|
self._log(
|
||||||
if price_info and price_info is not None:
|
"warning",
|
||||||
current = price_info.get("current")
|
"User data validation failed: Home %s has no active subscription",
|
||||||
if current and current is not None:
|
home_id,
|
||||||
currency = current.get("currency")
|
)
|
||||||
if not currency:
|
return False
|
||||||
self._log(
|
|
||||||
"warning",
|
price_info = subscription.get("priceInfo")
|
||||||
"User data validation failed: Home %s has subscription but no currency",
|
if not price_info:
|
||||||
home_id,
|
self._log(
|
||||||
)
|
"warning",
|
||||||
return False
|
"User data validation failed: Home %s subscription has no priceInfo",
|
||||||
|
home_id,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
current = price_info.get("current")
|
||||||
|
if not current:
|
||||||
|
self._log(
|
||||||
|
"warning",
|
||||||
|
"User data validation failed: Home %s priceInfo has no current data",
|
||||||
|
home_id,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
currency = current.get("currency")
|
||||||
|
if not currency:
|
||||||
|
self._log(
|
||||||
|
"warning",
|
||||||
|
"User data validation failed: Home %s has no currency",
|
||||||
|
home_id,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
@ -419,6 +441,10 @@ class TibberPricesPriceDataManager:
|
||||||
"""
|
"""
|
||||||
Get currency for a specific home from cached user_data.
|
Get currency for a specific home from cached user_data.
|
||||||
|
|
||||||
|
Note: The cached user_data is validated before storage, so if we have
|
||||||
|
cached data it should contain valid currency. This method extracts
|
||||||
|
the currency from the nested structure.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Currency code (e.g., "EUR", "NOK", "SEK").
|
Currency code (e.g., "EUR", "NOK", "SEK").
|
||||||
|
|
||||||
|
|
@ -444,7 +470,7 @@ class TibberPricesPriceDataManager:
|
||||||
currency = current.get("currency")
|
currency = current.get("currency")
|
||||||
|
|
||||||
if not currency:
|
if not currency:
|
||||||
# Home without active subscription - cannot determine currency
|
# This should not happen if validation worked correctly
|
||||||
msg = f"Home {home_id} has no active subscription - currency unavailable"
|
msg = f"Home {home_id} has no active subscription - currency unavailable"
|
||||||
self._log("error", msg)
|
self._log("error", msg)
|
||||||
raise TibberPricesApiClientError(msg)
|
raise TibberPricesApiClientError(msg)
|
||||||
|
|
|
||||||
|
|
@ -89,14 +89,21 @@ def test_validate_user_data_complete(mock_api_client, mock_time_service, mock_st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert price_data_manager._validate_user_data(user_data, "home-123") is True # noqa: SLF001 # noqa: SLF001
|
assert price_data_manager._validate_user_data(user_data, "home-123") is True # noqa: SLF001
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
def test_validate_user_data_none_subscription(
|
def test_validate_user_data_none_subscription(
|
||||||
mock_api_client: Mock, mock_time_service: Mock, mock_store: Mock, mock_interval_pool: Mock
|
mock_api_client: Mock, mock_time_service: Mock, mock_store: Mock, mock_interval_pool: Mock
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that user data without subscription (but with timezone) passes validation."""
|
"""
|
||||||
|
Test that user data without subscription fails validation.
|
||||||
|
|
||||||
|
Currency is required for the integration to function - if the API returns
|
||||||
|
data without a subscription, we cannot extract the currency and must reject
|
||||||
|
the data. This ensures we keep using previously cached valid data instead
|
||||||
|
of accepting incomplete API responses.
|
||||||
|
"""
|
||||||
price_data_manager = TibberPricesPriceDataManager(
|
price_data_manager = TibberPricesPriceDataManager(
|
||||||
api=mock_api_client,
|
api=mock_api_client,
|
||||||
store=mock_store,
|
store=mock_store,
|
||||||
|
|
@ -119,8 +126,8 @@ def test_validate_user_data_none_subscription(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Should pass validation - timezone is present, subscription being None is valid
|
# Should FAIL validation - subscription is required for currency
|
||||||
assert price_data_manager._validate_user_data(user_data, "home-123") is True # noqa: SLF001 # noqa: SLF001
|
assert price_data_manager._validate_user_data(user_data, "home-123") is False # noqa: SLF001
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
|
|
@ -217,7 +224,71 @@ def test_validate_user_data_home_not_found(mock_api_client, mock_time_service, m
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert price_data_manager._validate_user_data(user_data, "home-123") is False # noqa: SLF001 # noqa: SLF001
|
assert price_data_manager._validate_user_data(user_data, "home-123") is False # noqa: SLF001
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_validate_user_data_rejects_missing_subscription(
|
||||||
|
mock_api_client: Mock, mock_time_service: Mock, mock_store: Mock, mock_interval_pool: Mock
|
||||||
|
) -> None:
|
||||||
|
"""Test that validation rejects user data when subscription is missing."""
|
||||||
|
price_data_manager = TibberPricesPriceDataManager(
|
||||||
|
api=mock_api_client,
|
||||||
|
store=mock_store,
|
||||||
|
log_prefix="[Test]",
|
||||||
|
user_update_interval=timedelta(days=1),
|
||||||
|
time=mock_time_service,
|
||||||
|
home_id="home-123",
|
||||||
|
interval_pool=mock_interval_pool,
|
||||||
|
)
|
||||||
|
|
||||||
|
# User data with missing subscription (temporary API issue)
|
||||||
|
user_data = {
|
||||||
|
"viewer": {
|
||||||
|
"homes": [
|
||||||
|
{
|
||||||
|
"id": "home-123",
|
||||||
|
"timeZone": "Europe/Berlin",
|
||||||
|
"currentSubscription": None, # No subscription - should be rejected
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validation should reject this data
|
||||||
|
assert price_data_manager._validate_user_data(user_data, "home-123") is False # noqa: SLF001
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_validate_user_data_rejects_missing_price_info(
|
||||||
|
mock_api_client: Mock, mock_time_service: Mock, mock_store: Mock, mock_interval_pool: Mock
|
||||||
|
) -> None:
|
||||||
|
"""Test that validation rejects user data when priceInfo is missing."""
|
||||||
|
price_data_manager = TibberPricesPriceDataManager(
|
||||||
|
api=mock_api_client,
|
||||||
|
store=mock_store,
|
||||||
|
log_prefix="[Test]",
|
||||||
|
user_update_interval=timedelta(days=1),
|
||||||
|
time=mock_time_service,
|
||||||
|
home_id="home-123",
|
||||||
|
interval_pool=mock_interval_pool,
|
||||||
|
)
|
||||||
|
|
||||||
|
user_data = {
|
||||||
|
"viewer": {
|
||||||
|
"homes": [
|
||||||
|
{
|
||||||
|
"id": "home-123",
|
||||||
|
"timeZone": "Europe/Berlin",
|
||||||
|
"currentSubscription": {
|
||||||
|
"priceInfo": None, # Missing priceInfo
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert price_data_manager._validate_user_data(user_data, "home-123") is False # noqa: SLF001
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
|
|
@ -237,7 +308,7 @@ def test_get_currency_raises_on_no_cached_data(
|
||||||
|
|
||||||
# No cached data
|
# No cached data
|
||||||
with pytest.raises(TibberPricesApiClientError, match="No user data cached"):
|
with pytest.raises(TibberPricesApiClientError, match="No user data cached"):
|
||||||
price_data_manager._get_currency_for_home("home-123") # noqa: SLF001 # noqa: SLF001
|
price_data_manager._get_currency_for_home("home-123") # noqa: SLF001
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
|
|
@ -255,7 +326,7 @@ def test_get_currency_raises_on_no_subscription(
|
||||||
interval_pool=mock_interval_pool,
|
interval_pool=mock_interval_pool,
|
||||||
)
|
)
|
||||||
|
|
||||||
price_data_manager._cached_user_data = { # noqa: SLF001 # noqa: SLF001
|
price_data_manager._cached_user_data = { # noqa: SLF001
|
||||||
"viewer": {
|
"viewer": {
|
||||||
"homes": [
|
"homes": [
|
||||||
{
|
{
|
||||||
|
|
@ -267,7 +338,7 @@ def test_get_currency_raises_on_no_subscription(
|
||||||
}
|
}
|
||||||
|
|
||||||
with pytest.raises(TibberPricesApiClientError, match="has no active subscription"):
|
with pytest.raises(TibberPricesApiClientError, match="has no active subscription"):
|
||||||
price_data_manager._get_currency_for_home("home-123") # noqa: SLF001 # noqa: SLF001
|
price_data_manager._get_currency_for_home("home-123") # noqa: SLF001
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
|
|
@ -285,7 +356,7 @@ def test_get_currency_extracts_valid_currency(
|
||||||
interval_pool=mock_interval_pool,
|
interval_pool=mock_interval_pool,
|
||||||
)
|
)
|
||||||
|
|
||||||
price_data_manager._cached_user_data = { # noqa: SLF001 # noqa: SLF001
|
price_data_manager._cached_user_data = { # noqa: SLF001
|
||||||
"viewer": {
|
"viewer": {
|
||||||
"homes": [
|
"homes": [
|
||||||
{
|
{
|
||||||
|
|
@ -302,7 +373,7 @@ def test_get_currency_extracts_valid_currency(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert price_data_manager._get_currency_for_home("home-123") == "NOK" # noqa: SLF001 # noqa: SLF001
|
assert price_data_manager._get_currency_for_home("home-123") == "NOK" # noqa: SLF001
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue