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:
Bobby Noelte
2026-07-22 14:50:55 +02:00
committed by GitHub
parent 8d72177671
commit c914bae667
12 changed files with 152 additions and 147 deletions

View File

@@ -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"]

View File

@@ -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."""

View File

@@ -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

View File

@@ -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",
[

View 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

View File

@@ -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,

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()