Files
EOS/tests/test_elecpriceakkudoktor.py
T

224 lines
7.4 KiB
Python
Raw Normal View History

import json
from pathlib import Path
from unittest.mock import Mock, patch
2024-12-29 18:42:49 +01:00
import numpy as np
import pytest
2024-12-29 18:42:49 +01:00
import requests
from loguru import logger
2025-02-12 21:35:51 +01:00
from akkudoktoreos.core.cache import CacheFileStore
from akkudoktoreos.core.coreabc import get_ems
from akkudoktoreos.prediction.elecpriceakkudoktor import (
AkkudoktorElecPrice,
AkkudoktorElecPriceValue,
ElecPriceAkkudoktor,
)
from akkudoktoreos.utils.datetimeutil import to_datetime
DIR_TESTDATA = Path(__file__).absolute().parent.joinpath("testdata")
FILE_TESTDATA_ELECPRICEAKKUDOKTOR_1_JSON = DIR_TESTDATA.joinpath(
"elecpriceforecast_akkudoktor_1.json"
)
@pytest.fixture
2025-01-18 14:26:34 +01:00
def provider(monkeypatch, config_eos):
"""Fixture to create a ElecPriceProvider instance."""
2025-01-12 05:19:37 +01:00
monkeypatch.setenv("EOS_ELECPRICE__ELECPRICE_PROVIDER", "ElecPriceAkkudoktor")
config_eos.reset_settings()
return ElecPriceAkkudoktor()
@pytest.fixture
def sample_akkudoktor_1_json():
"""Fixture that returns sample forecast data report."""
2025-02-12 21:35:51 +01:00
with FILE_TESTDATA_ELECPRICEAKKUDOKTOR_1_JSON.open(
"r", encoding="utf-8", newline=None
) as f_res:
input_data = json.load(f_res)
return input_data
@pytest.fixture
def cache_store():
"""A pytest fixture that creates a new CacheFileStore instance for testing."""
return CacheFileStore()
# ------------------------------------------------
# General forecast
# ------------------------------------------------
2025-01-18 14:26:34 +01:00
def test_singleton_instance(provider):
"""Test that ElecPriceForecast behaves as a singleton."""
another_instance = ElecPriceAkkudoktor()
2025-01-18 14:26:34 +01:00
assert provider is another_instance
2025-01-18 14:26:34 +01:00
def test_invalid_provider(provider, monkeypatch):
"""Test requesting an unsupported provider."""
2025-01-12 05:19:37 +01:00
monkeypatch.setenv("EOS_ELECPRICE__ELECPRICE_PROVIDER", "<invalid>")
2025-01-18 14:26:34 +01:00
provider.config.reset_settings()
assert not provider.enabled()
# ------------------------------------------------
# Akkudoktor
# ------------------------------------------------
2024-12-29 18:42:49 +01:00
@patch("akkudoktoreos.prediction.elecpriceakkudoktor.logger.error")
2025-01-18 14:26:34 +01:00
def test_validate_data_invalid_format(mock_logger, provider):
2024-12-29 18:42:49 +01:00
"""Test validation for invalid Akkudoktor data."""
invalid_data = '{"invalid": "data"}'
with pytest.raises(ValueError):
2025-01-18 14:26:34 +01:00
provider._validate_data(invalid_data)
2024-12-29 18:42:49 +01:00
mock_logger.assert_called_once_with(mock_logger.call_args[0][0])
@patch("requests.get")
2025-01-18 14:26:34 +01:00
def test_request_forecast(mock_get, provider, sample_akkudoktor_1_json):
"""Test requesting forecast from Akkudoktor."""
# Mock response object
mock_response = Mock()
mock_response.status_code = 200
mock_response.content = json.dumps(sample_akkudoktor_1_json)
mock_get.return_value = mock_response
# Test function
2025-01-18 14:26:34 +01:00
akkudoktor_data = provider._request_forecast()
assert isinstance(akkudoktor_data, AkkudoktorElecPrice)
assert akkudoktor_data.values[0].model_dump() == AkkudoktorElecPriceValue(
2025-01-02 15:22:18 +01:00
start_timestamp=1733785200000,
end_timestamp=1733788800000,
start="2024-12-09T23:00:00.000Z",
end="2024-12-10T00:00:00.000Z",
marketprice=92.85,
unit="Eur/MWh",
2025-01-02 15:22:18 +01:00
marketpriceEurocentPerKWh=9.29,
).model_dump()
@patch("requests.get")
2025-01-18 14:26:34 +01:00
def test_update_data(mock_get, provider, sample_akkudoktor_1_json, cache_store):
"""Test fetching forecast from Akkudoktor."""
# Mock response object
mock_response = Mock()
mock_response.status_code = 200
mock_response.content = json.dumps(sample_akkudoktor_1_json)
mock_get.return_value = mock_response
cache_store.clear(clear_all=True)
# Call the method
ems_eos = get_ems()
ems_eos.set_start_datetime(to_datetime("2024-12-11 00:00:00", in_timezone="Europe/Berlin"))
2025-01-18 14:26:34 +01:00
provider.update_data(force_enable=True, force_update=True)
# Assert: Verify the result is as expected
mock_get.assert_called_once()
# The API response holds 48 datasets, 23 h of which already reach into the
# future, so the provider forecasts the remaining hours of the horizon itself.
assert len(provider) == 48 + provider.config.prediction.hours - 23
2025-01-18 14:26:34 +01:00
# Assert we get hours prioce values by resampling
np_price_array = provider.key_to_array(
2024-12-30 21:29:50 +01:00
key="elecprice_marketprice_wh",
2025-10-28 02:50:31 +01:00
start_datetime=provider.ems_start_datetime,
2025-01-18 14:26:34 +01:00
end_datetime=provider.end_datetime,
)
2025-01-18 14:26:34 +01:00
assert len(np_price_array) == provider.total_hours
# with open(FILE_TESTDATA_ELECPRICEAKKUDOKTOR_2_JSON, "w") as f_out:
2025-01-18 14:26:34 +01:00
# f_out.write(provider.to_json())
2024-12-29 18:42:49 +01:00
@patch("requests.get")
2025-01-18 14:26:34 +01:00
def test_update_data_with_incomplete_forecast(mock_get, provider):
2024-12-29 18:42:49 +01:00
"""Test `_update_data` with incomplete or missing forecast data."""
incomplete_data: dict = {"meta": {}, "values": []}
mock_response = Mock()
mock_response.status_code = 200
mock_response.content = json.dumps(incomplete_data)
mock_get.return_value = mock_response
logger.info("The following errors are intentional and part of the test.")
2024-12-29 18:42:49 +01:00
with pytest.raises(ValueError):
2025-01-18 14:26:34 +01:00
provider._update_data(force_update=True)
2024-12-29 18:42:49 +01:00
@pytest.mark.parametrize(
"status_code, exception",
[(400, requests.exceptions.HTTPError), (500, requests.exceptions.HTTPError), (200, None)],
)
@patch("requests.get")
def test_request_forecast_status_codes(
2025-01-18 14:26:34 +01:00
mock_get, provider, sample_akkudoktor_1_json, status_code, exception
2024-12-29 18:42:49 +01:00
):
"""Test handling of various API status codes."""
mock_response = Mock()
mock_response.status_code = status_code
mock_response.content = json.dumps(sample_akkudoktor_1_json)
mock_response.raise_for_status.side_effect = (
requests.exceptions.HTTPError if exception else None
)
mock_get.return_value = mock_response
if exception:
with pytest.raises(exception):
2025-01-18 14:26:34 +01:00
provider._request_forecast()
2024-12-29 18:42:49 +01:00
else:
2025-01-18 14:26:34 +01:00
provider._request_forecast()
2024-12-29 18:42:49 +01:00
2025-11-13 15:13:37 +01:00
@patch("requests.get")
2025-02-12 21:35:51 +01:00
@patch("akkudoktoreos.core.cache.CacheFileStore")
2025-11-13 15:13:37 +01:00
def test_cache_integration(mock_cache, mock_get, provider, sample_akkudoktor_1_json):
2024-12-29 18:42:49 +01:00
"""Test caching of 8-day electricity price data."""
2025-11-13 15:13:37 +01:00
# Mock response object
mock_response = Mock()
mock_response.status_code = 200
mock_response.content = json.dumps(sample_akkudoktor_1_json)
mock_get.return_value = mock_response
# Mock cache object
2024-12-29 18:42:49 +01:00
mock_cache_instance = mock_cache.return_value
mock_cache_instance.get.return_value = None # Simulate no cache
2025-11-13 15:13:37 +01:00
2025-01-18 14:26:34 +01:00
provider._update_data(force_update=True)
2024-12-29 18:42:49 +01:00
mock_cache_instance.create.assert_called_once()
mock_cache_instance.get.assert_called_once()
2025-01-18 14:26:34 +01:00
def test_key_to_array_resampling(provider):
2024-12-29 18:42:49 +01:00
"""Test resampling of forecast data to NumPy array."""
2025-01-18 14:26:34 +01:00
provider.update_data(force_update=True)
array = provider.key_to_array(
2024-12-30 21:29:50 +01:00
key="elecprice_marketprice_wh",
2025-10-28 02:50:31 +01:00
start_datetime=provider.ems_start_datetime,
2025-01-18 14:26:34 +01:00
end_datetime=provider.end_datetime,
2024-12-29 18:42:49 +01:00
)
assert isinstance(array, np.ndarray)
2025-01-18 14:26:34 +01:00
assert len(array) == provider.total_hours
2024-12-29 18:42:49 +01:00
# ------------------------------------------------
# Development Akkudoktor
# ------------------------------------------------
@pytest.mark.skip(reason="For development only")
2025-01-18 14:26:34 +01:00
def test_akkudoktor_development_forecast_data(provider):
"""Fetch data from real Akkudoktor server."""
# Preset, as this is usually done by update_data()
2025-10-28 02:50:31 +01:00
provider.ems_start_datetime = to_datetime("2024-10-26 00:00:00")
2025-01-18 14:26:34 +01:00
akkudoktor_data = provider._request_forecast()
2025-02-12 21:35:51 +01:00
with FILE_TESTDATA_ELECPRICEAKKUDOKTOR_1_JSON.open(
"w", encoding="utf-8", newline="\n"
) as f_out:
json.dump(akkudoktor_data, f_out, indent=4)