mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-07-24 10:48:11 +00:00
fix: data management conversion to async (#1185)
Fix test warnings left over from data management conversion to async design. Mostly tagging async tests. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
This commit is contained in:
@@ -38,7 +38,6 @@ def adapter(config_eos, mock_ems: MagicMock) -> NodeREDAdapter:
|
||||
return ad
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestNodeREDAdapter:
|
||||
|
||||
def test_provider_id(self, adapter: NodeREDAdapter):
|
||||
@@ -52,6 +51,7 @@ class TestNodeREDAdapter:
|
||||
adapter.config.adapter.provider = ["HomeAssistant", "NodeRED"]
|
||||
assert adapter.enabled() is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch("requests.get")
|
||||
async def test_update_datetime(self, mock_get, adapter: NodeREDAdapter):
|
||||
adapter.ems.stage.return_value = EnergyManagementStage.DATA_ACQUISITION
|
||||
@@ -64,6 +64,7 @@ class TestNodeREDAdapter:
|
||||
mock_get.assert_called_once()
|
||||
assert compare_datetimes(adapter.update_datetime, now).approximately_equal
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch("requests.get")
|
||||
async def test_update_data_data_acquisition_success(self, mock_get , adapter: NodeREDAdapter):
|
||||
adapter.ems.stage.return_value = EnergyManagementStage.DATA_ACQUISITION
|
||||
@@ -76,12 +77,14 @@ class TestNodeREDAdapter:
|
||||
url, = mock_get.call_args[0]
|
||||
assert "/eos/data_aquisition" in url
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch("requests.get", side_effect=Exception("boom"))
|
||||
async def test_update_data_data_acquisition_failure(self, mock_get, adapter: NodeREDAdapter):
|
||||
adapter.ems.stage.return_value = EnergyManagementStage.DATA_ACQUISITION
|
||||
with pytest.raises(RuntimeError):
|
||||
await adapter.update_data(force_enable=True)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch("requests.post")
|
||||
async def test_update_data_control_dispatch_instructions(self, mock_post, adapter: NodeREDAdapter):
|
||||
adapter.ems.stage.return_value = EnergyManagementStage.CONTROL_DISPATCH
|
||||
@@ -110,12 +113,14 @@ class TestNodeREDAdapter:
|
||||
url, = mock_post.call_args[0]
|
||||
assert "/eos/control_dispatch" in url
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch("requests.post")
|
||||
async def test_update_data_disabled_provider(self, mock_post, adapter: NodeREDAdapter):
|
||||
adapter.config.adapter.provider = ["HomeAssistant"] # NodeRED disabled
|
||||
await adapter.update_data(force_enable=False)
|
||||
mock_post.assert_not_called()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch("requests.post")
|
||||
async def test_update_data_force_enable_overrides_disabled(self, mock_post, adapter: NodeREDAdapter):
|
||||
adapter.config.adapter.provider = ["HomeAssistant"]
|
||||
|
||||
@@ -52,7 +52,6 @@ def cache_store():
|
||||
return CacheFileStore()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestElecPriceEnergyCharts:
|
||||
# ------------------------------------------------
|
||||
# General forecast
|
||||
@@ -63,19 +62,16 @@ class TestElecPriceEnergyCharts:
|
||||
another_instance = ElecPriceEnergyCharts()
|
||||
assert provider is another_instance
|
||||
|
||||
|
||||
def test_invalid_provider(self, provider, monkeypatch):
|
||||
"""Test requesting an unsupported provider."""
|
||||
monkeypatch.setenv("EOS_ELECPRICE__ELECPRICE_PROVIDER", "<invalid>")
|
||||
provider.config.reset_settings()
|
||||
assert not provider.enabled()
|
||||
|
||||
|
||||
# ------------------------------------------------
|
||||
# Akkudoktor
|
||||
# ------------------------------------------------
|
||||
|
||||
|
||||
@patch("akkudoktoreos.prediction.elecpriceenergycharts.logger.error")
|
||||
def test_validate_data_invalid_format(self, mock_logger, provider):
|
||||
"""Test validation for invalid Energy-Charts data."""
|
||||
@@ -84,7 +80,6 @@ class TestElecPriceEnergyCharts:
|
||||
provider._validate_data(invalid_data)
|
||||
mock_logger.assert_called_once_with(mock_logger.call_args[0][0])
|
||||
|
||||
|
||||
@patch("requests.get")
|
||||
def test_request_forecast(self, mock_get, provider, sample_energycharts_json):
|
||||
"""Test requesting forecast from Energy-Charts."""
|
||||
@@ -101,7 +96,7 @@ class TestElecPriceEnergyCharts:
|
||||
assert energy_charts_data.unix_seconds[0] == 1733785200
|
||||
assert energy_charts_data.price[0] == 92.85
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch("requests.get")
|
||||
async def test_update_data(self, mock_get, provider, sample_energycharts_json, cache_store):
|
||||
"""Test fetching forecast from Energy-Charts."""
|
||||
@@ -133,7 +128,7 @@ class TestElecPriceEnergyCharts:
|
||||
)
|
||||
assert len(np_price_array) == provider.total_hours
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch("requests.get")
|
||||
async def test_update_data_with_incomplete_forecast(self, mock_get, provider):
|
||||
"""Test `_update_data` with incomplete or missing forecast data."""
|
||||
@@ -146,7 +141,6 @@ class TestElecPriceEnergyCharts:
|
||||
with pytest.raises(ValueError):
|
||||
await provider._update_data(force_update=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"status_code, exception",
|
||||
[(400, requests.exceptions.HTTPError), (500, requests.exceptions.HTTPError), (200, None)],
|
||||
@@ -169,7 +163,7 @@ class TestElecPriceEnergyCharts:
|
||||
else:
|
||||
provider._request_forecast()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch("requests.get")
|
||||
@patch("akkudoktoreos.core.cache.CacheFileStore")
|
||||
async def test_cache_integration(self, mock_cache, mock_get, provider, sample_energycharts_json):
|
||||
@@ -188,7 +182,7 @@ class TestElecPriceEnergyCharts:
|
||||
mock_cache_instance.create.assert_called_once()
|
||||
mock_cache_instance.get.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_key_to_array_resampling(self, provider):
|
||||
"""Test resampling of forecast data to NumPy array."""
|
||||
await provider.update_data(force_update=True)
|
||||
@@ -201,7 +195,6 @@ class TestElecPriceEnergyCharts:
|
||||
assert isinstance(array, np.ndarray)
|
||||
assert len(array) == provider.total_hours
|
||||
|
||||
|
||||
@patch("requests.get")
|
||||
def test_request_forecast_url_bidding_zone_is_value(self, mock_get, provider, sample_energycharts_json):
|
||||
"""Test that the bidding zone in the API URL uses the enum *value* (e.g. 'DE-LU'),
|
||||
@@ -238,12 +231,10 @@ class TestElecPriceEnergyCharts:
|
||||
f"but got bzn='{bzn_value}' in URL: {actual_url}"
|
||||
)
|
||||
|
||||
|
||||
# ------------------------------------------------
|
||||
# Development Energy Charts
|
||||
# ------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="For development only")
|
||||
def test_energycharts_development_forecast_data(self, provider):
|
||||
"""Fetch data from real Energy-Charts server."""
|
||||
|
||||
@@ -92,7 +92,6 @@ def cache_store():
|
||||
return CacheFileStore()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestElecPriceFixed:
|
||||
"""Tests for ElecPriceFixed provider."""
|
||||
|
||||
@@ -111,6 +110,7 @@ class TestElecPriceFixed:
|
||||
provider.config.reset_settings()
|
||||
assert not provider.enabled()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_data_hourly_intervals(self, provider, config_eos):
|
||||
"""Test updating data with hourly intervals (3600s)."""
|
||||
# Set start datetime
|
||||
@@ -142,6 +142,7 @@ class TestElecPriceFixed:
|
||||
for i in range(8, 24):
|
||||
assert abs(records[i].elecprice_marketprice_wh - 0.00034) < 1e-6
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_data_15min_intervals(self, provider, config_eos):
|
||||
"""Test updating data with 15-minute intervals (900s)."""
|
||||
ems_eos = get_ems()
|
||||
@@ -175,6 +176,7 @@ class TestElecPriceFixed:
|
||||
f"Expected day rate at interval {i}, got {records[i].elecprice_marketprice_wh}"
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_data_30min_intervals(self, provider, config_eos):
|
||||
"""Test updating data with 30-minute intervals (1800s)."""
|
||||
ems_eos = get_ems()
|
||||
@@ -208,6 +210,7 @@ class TestElecPriceFixed:
|
||||
f"Expected day rate at interval {i}, got {records[i].elecprice_marketprice_wh}"
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_data_without_config(self, provider, config_eos):
|
||||
"""Test update_data fails without configuration."""
|
||||
# Remove elecpricefixed settings
|
||||
@@ -216,6 +219,7 @@ class TestElecPriceFixed:
|
||||
with pytest.raises(ValueError, match="No time windows configured"):
|
||||
await provider.update_data(force_enable=True, force_update=True)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_data_without_time_windows(self, provider, config_eos):
|
||||
"""Test update_data fails without time windows."""
|
||||
# Set empty time windows
|
||||
@@ -225,6 +229,7 @@ class TestElecPriceFixed:
|
||||
with pytest.raises(ValueError, match="No time windows configured"):
|
||||
await provider.update_data(force_enable=True, force_update=True)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_key_to_array_resampling(self, provider, config_eos):
|
||||
"""Test that key_to_array can resample to different intervals."""
|
||||
# Setup provider with hourly data
|
||||
|
||||
@@ -40,7 +40,6 @@ def sample_import_1_json():
|
||||
return input_data
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestElecPriceImport:
|
||||
# ------------------------------------------------
|
||||
# General forecast
|
||||
@@ -72,6 +71,7 @@ class TestElecPriceImport:
|
||||
# ------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"start_datetime, from_file",
|
||||
[
|
||||
|
||||
@@ -36,7 +36,6 @@ def compare_dict(actual: dict[str, Any], expected: dict[str, Any]):
|
||||
else:
|
||||
assert actual[key] == pytest.approx(value)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"fn_in, fn_out, ngen, break_even",
|
||||
@@ -48,7 +47,7 @@ def compare_dict(actual: dict[str, Any], expected: dict[str, Any]):
|
||||
("optimize_input_2.json", "optimize_result_2_be.json", 3, 1),
|
||||
],
|
||||
)
|
||||
def test_optimize(
|
||||
async def test_optimize(
|
||||
fn_in: str,
|
||||
fn_out: str,
|
||||
ngen: int,
|
||||
@@ -147,7 +146,7 @@ def test_optimize(
|
||||
compare_dict(genetic_solution.model_dump(), expected_result.model_dump())
|
||||
|
||||
# Check the correct generic optimization solution is created
|
||||
optimization_solution = genetic_solution.optimization_solution()
|
||||
optimization_solution = await genetic_solution.optimization_solution()
|
||||
# @TODO
|
||||
|
||||
# Check the correct generic energy management plan is created
|
||||
|
||||
@@ -49,9 +49,9 @@ def mock_forecast_response():
|
||||
totals={}
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestLoadVRM:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_data_calls_update_value(self, load_vrm_instance):
|
||||
with patch.object(load_vrm_instance, "_request_forecast", return_value=mock_forecast_response()), \
|
||||
patch.object(LoadVrm, "update_value") as mock_update:
|
||||
@@ -100,6 +100,7 @@ class TestLoadVRM:
|
||||
assert "Failed to fetch load forecast" in str(exc_info.value)
|
||||
mock_get.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_data_does_nothing_on_empty_forecast(self, load_vrm_instance):
|
||||
empty_response = VrmForecastResponse(
|
||||
success=True,
|
||||
|
||||
@@ -114,11 +114,12 @@ def test_request_forecast_sums_planes(config_eos):
|
||||
assert body["watts"]["2025-01-01 12:00:00"] == 1800.0
|
||||
|
||||
|
||||
def test_update_data_skips_when_disabled(pvforecast_instance, config_eos):
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_data_skips_when_disabled(pvforecast_instance, config_eos):
|
||||
config_eos.merge_settings_from_dict({"pvforecast": {"provider": "PVForecastAkkudoktor"}})
|
||||
with patch.object(pvforecast_instance, "_request_forecast") as mock_req, \
|
||||
patch.object(PVForecastForecastSolar, "update_value") as mock_update:
|
||||
pvforecast_instance._update_data()
|
||||
await pvforecast_instance._update_data()
|
||||
mock_req.assert_not_called()
|
||||
mock_update.assert_not_called()
|
||||
|
||||
|
||||
@@ -87,19 +87,21 @@ async def test_update_data_sets_ac_and_dc_power(pvforecast_instance):
|
||||
mock_update.assert_has_calls(expected, any_order=False)
|
||||
|
||||
|
||||
def test_update_data_skips_when_disabled(pvforecast_instance, config_eos):
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_data_skips_when_disabled(pvforecast_instance, config_eos):
|
||||
config_eos.merge_settings_from_dict({"pvforecast": {"provider": "PVForecastAkkudoktor"}})
|
||||
with patch.object(pvforecast_instance, "_request_forecast") as mock_req, \
|
||||
patch.object(PVForecastPVNode, "update_value") as mock_update:
|
||||
pvforecast_instance._update_data()
|
||||
await pvforecast_instance._update_data()
|
||||
mock_req.assert_not_called()
|
||||
mock_update.assert_not_called()
|
||||
|
||||
|
||||
def test_update_data_skips_on_empty_forecast(pvforecast_instance):
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_data_skips_on_empty_forecast(pvforecast_instance):
|
||||
with patch.object(pvforecast_instance, "_request_forecast", return_value={"values": []}), \
|
||||
patch.object(PVForecastPVNode, "update_value") as mock_update:
|
||||
pvforecast_instance._update_data()
|
||||
await pvforecast_instance._update_data()
|
||||
mock_update.assert_not_called()
|
||||
|
||||
|
||||
|
||||
@@ -80,11 +80,12 @@ def test_request_forecast_uses_site_and_bearer(pvforecast_instance):
|
||||
assert mock_get.call_args.kwargs["params"]["format"] == "json"
|
||||
|
||||
|
||||
def test_update_data_skips_when_disabled(pvforecast_instance, config_eos):
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_data_skips_when_disabled(pvforecast_instance, config_eos):
|
||||
config_eos.merge_settings_from_dict({"pvforecast": {"provider": "PVForecastAkkudoktor"}})
|
||||
with patch.object(pvforecast_instance, "_request_forecast") as mock_req, \
|
||||
patch.object(PVForecastSolcast, "update_value") as mock_update:
|
||||
pvforecast_instance._update_data()
|
||||
await pvforecast_instance._update_data()
|
||||
mock_req.assert_not_called()
|
||||
mock_update.assert_not_called()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user