Reasonable defaults, isolate tests, EOS_LOGGING_LEVEL, EOS_CONFIG_DIR

* Add EOS_CONFIG_DIR to set config dir (relative path to EOS_DIR or
   absolute path).
    - config_folder_path read-only
    - config_file_path read-only
 * Default values to support app start with empty config:
    - latitude/longitude (Berlin)
    - optimization_ev_available_charge_rates_percent (null, so model
      default value is used)
    - Enable Akkudoktor electricity price forecast (docker-compose).
 * Fix some endpoints (empty data, remove unused params, fix types).
 * cacheutil: Use cache dir. Closes #240
 * Support EOS_LOGGING_LEVEL environment variable to set log level.
 * tests: All tests use separate temporary config
    - Add pytest switch --check-config-side-effect to check user
      config file existence after each test. Will also fail if user config
      existed before test execution (but will only check after the test has
      run).
      Enable flag in github workflow.
    - Globally mock platformdirs in config module. Now no longer required
      to patch individually.
      Function calls to config instance (e.g. merge_settings_from_dict)
      were unaffected previously.
 * Set Berlin as default location (default config/docker-compose).
This commit is contained in:
Dominique Lasserre
2024-12-30 13:41:39 +01:00
committed by GitHub
parent 267a9bf427
commit 75987db9e1
29 changed files with 373 additions and 375 deletions

View File

@@ -1,17 +1,17 @@
import logging
import shutil
import os
import subprocess
import sys
import tempfile
from pathlib import Path
from typing import Optional
from unittest.mock import PropertyMock, patch
import pendulum
import platformdirs
import pytest
from xprocess import ProcessStarter
from akkudoktoreos.config.config import get_config
from akkudoktoreos.config.config import ConfigEOS, get_config
from akkudoktoreos.utils.logutil import get_logger
logger = get_logger(__name__)
@@ -42,6 +42,12 @@ def pytest_addoption(parser):
parser.addoption(
"--full-run", action="store_true", default=False, help="Run with all optimization tests."
)
parser.addoption(
"--check-config-side-effect",
action="store_true",
default=False,
help="Verify that user config file is non-existent (will also fail if user config file exists before test run).",
)
@pytest.fixture
@@ -49,97 +55,107 @@ def is_full_run(request):
yield bool(request.config.getoption("--full-run"))
@pytest.fixture(autouse=True)
def config_mixin(config_eos):
with patch(
"akkudoktoreos.core.coreabc.ConfigMixin.config", new_callable=PropertyMock
) as config_mixin_patch:
config_mixin_patch.return_value = config_eos
yield config_mixin_patch
# Test if test has side effect of writing to system (user) config file
# Before activating, make sure that no user config file exists (e.g. ~/.config/net.akkudoktoreos.eos/EOS.config.json)
@pytest.fixture(autouse=True)
def cfg_non_existent(request):
yield
if bool(request.config.getoption("--check-config-side-effect")):
from platformdirs import user_config_dir
user_dir = user_config_dir(ConfigEOS.APP_NAME)
assert not Path(user_dir).joinpath(ConfigEOS.CONFIG_FILE_NAME).exists()
@pytest.fixture(autouse=True)
def user_config_dir(config_default_dirs):
with patch(
"akkudoktoreos.config.config.user_config_dir",
return_value=str(config_default_dirs[0]),
) as user_dir_patch:
yield user_dir_patch
@pytest.fixture(autouse=True)
def user_data_dir(config_default_dirs):
with patch(
"akkudoktoreos.config.config.user_data_dir",
return_value=str(config_default_dirs[-1] / "data"),
) as user_dir_patch:
yield user_dir_patch
@pytest.fixture
def reset_config(disable_debug_logging):
def config_eos(
disable_debug_logging,
user_config_dir,
user_data_dir,
config_default_dirs,
monkeypatch,
) -> ConfigEOS:
"""Fixture to reset EOS config to default values."""
monkeypatch.setenv("data_cache_subpath", str(config_default_dirs[-1] / "data/cache"))
monkeypatch.setenv("data_output_subpath", str(config_default_dirs[-1] / "data/output"))
config_file = config_default_dirs[0] / ConfigEOS.CONFIG_FILE_NAME
assert not config_file.exists()
config_eos = get_config()
config_eos.reset_settings()
config_eos.reset_to_defaults()
assert config_file == config_eos.config_file_path
assert config_file.exists()
assert config_default_dirs[-1] / "data" == config_eos.data_folder_path
assert config_default_dirs[-1] / "data/cache" == config_eos.data_cache_path
assert config_default_dirs[-1] / "data/output" == config_eos.data_output_path
return config_eos
@pytest.fixture
def config_default_dirs():
"""Fixture that provides a list of directories to be used as config dir."""
config_eos = get_config()
# Default config directory from platform user config directory
config_default_dir_user = Path(platformdirs.user_config_dir(config_eos.APP_NAME))
# Default config directory from current working directory
config_default_dir_cwd = Path.cwd()
# Default config directory from default config file
config_default_dir_default = Path(__file__).parent.parent.joinpath("src/akkudoktoreos/data")
return config_default_dir_user, config_default_dir_cwd, config_default_dir_default
with tempfile.TemporaryDirectory() as tmp_user_home_dir:
# Default config directory from platform user config directory
config_default_dir_user = Path(tmp_user_home_dir) / "config"
# Default config directory from current working directory
config_default_dir_cwd = Path.cwd()
# Default config directory from default config file
config_default_dir_default = Path(__file__).parent.parent.joinpath("src/akkudoktoreos/data")
# Default data directory from platform user data directory
data_default_dir_user = Path(tmp_user_home_dir)
yield (
config_default_dir_user,
config_default_dir_cwd,
config_default_dir_default,
data_default_dir_user,
)
@pytest.fixture
def stash_config_file(config_default_dirs):
"""Fixture to temporarily stash away an existing config file during a test.
If the specified config file exists, it moves the file to a temporary directory.
The file is restored to its original location after the test.
Keep right most in fixture parameter list to assure application at last.
Returns:
Path: Path to the stashed config file.
"""
config_eos = get_config()
config_default_dir_user, config_default_dir_cwd, _ = config_default_dirs
config_file_path_user = config_default_dir_user.joinpath(config_eos.CONFIG_FILE_NAME)
config_file_path_cwd = config_default_dir_cwd.joinpath(config_eos.CONFIG_FILE_NAME)
original_config_file_user = None
original_config_file_cwd = None
if config_file_path_user.exists():
original_config_file_user = config_file_path_user
if config_file_path_cwd.exists():
original_config_file_cwd = config_file_path_cwd
temp_dir = tempfile.TemporaryDirectory()
temp_file_user = None
temp_file_cwd = None
# If the file exists, move it to the temporary directory
if original_config_file_user:
temp_file_user = Path(temp_dir.name) / f"user.{original_config_file_user.name}"
shutil.move(original_config_file_user, temp_file_user)
assert not original_config_file_user.exists()
logger.debug(f"Stashed: '{original_config_file_user}'")
if original_config_file_cwd:
temp_file_cwd = Path(temp_dir.name) / f"cwd.{original_config_file_cwd.name}"
shutil.move(original_config_file_cwd, temp_file_cwd)
assert not original_config_file_cwd.exists()
logger.debug(f"Stashed: '{original_config_file_cwd}'")
# Yield the temporary file path to the test
yield temp_file_user, temp_file_cwd
# Cleanup after the test
if temp_file_user:
# Restore the file to its original location
shutil.move(temp_file_user, original_config_file_user)
assert original_config_file_user.exists()
if temp_file_cwd:
# Restore the file to its original location
shutil.move(temp_file_cwd, original_config_file_cwd)
assert original_config_file_cwd.exists()
temp_dir.cleanup()
@pytest.fixture
def server(xprocess, tmp_path: Path):
def server(xprocess, config_eos, config_default_dirs):
"""Fixture to start the server.
Provides URL of the server.
"""
class Starter(ProcessStarter):
# Set environment before any subprocess run, to keep custom config dir
env = os.environ.copy()
env["EOS_DIR"] = str(config_default_dirs[-1])
# assure server to be installed
try:
subprocess.run(
[sys.executable, "-c", "import akkudoktoreos.server.fastapi_server"],
check=True,
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
@@ -154,11 +170,6 @@ def server(xprocess, tmp_path: Path):
# command to start server process
args = [sys.executable, "-m", "akkudoktoreos.server.fastapi_server"]
config_eos = get_config()
settings = {
"data_folder_path": tmp_path,
}
config_eos.merge_settings_from_dict(settings)
# startup pattern
pattern = "Application startup complete."
@@ -174,6 +185,7 @@ def server(xprocess, tmp_path: Path):
# ensure process is running and return its logfile
pid, logfile = xprocess.ensure("eos", Starter)
print(f"View xprocess logfile at: {logfile}")
# create url/port info to the server
url = "http://127.0.0.1:8503"

View File

@@ -3,7 +3,6 @@ from pathlib import Path
import numpy as np
import pytest
from akkudoktoreos.config.config import get_config
from akkudoktoreos.core.ems import (
EnergieManagementSystem,
EnergieManagementSystemParameters,
@@ -24,10 +23,9 @@ start_hour = 1
# Example initialization of necessary components
@pytest.fixture
def create_ems_instance() -> EnergieManagementSystem:
def create_ems_instance(config_eos) -> EnergieManagementSystem:
"""Fixture to create an EnergieManagementSystem instance with given test parameters."""
# Assure configuration holds the correct values
config_eos = get_config()
config_eos.merge_settings_from_dict({"prediction_hours": 48, "optimization_hours": 24})
assert config_eos.prediction_hours is not None

View File

@@ -3,7 +3,6 @@ from pathlib import Path
import numpy as np
import pytest
from akkudoktoreos.config.config import get_config
from akkudoktoreos.core.ems import (
EnergieManagementSystem,
EnergieManagementSystemParameters,
@@ -23,10 +22,9 @@ start_hour = 0
# Example initialization of necessary components
@pytest.fixture
def create_ems_instance() -> EnergieManagementSystem:
def create_ems_instance(config_eos) -> EnergieManagementSystem:
"""Fixture to create an EnergieManagementSystem instance with given test parameters."""
# Assure configuration holds the correct values
config_eos = get_config()
config_eos.merge_settings_from_dict({"prediction_hours": 48, "optimization_hours": 24})
assert config_eos.prediction_hours is not None

View File

@@ -5,7 +5,7 @@ from unittest.mock import patch
import pytest
from akkudoktoreos.config.config import get_config
from akkudoktoreos.config.config import ConfigEOS
from akkudoktoreos.optimization.genetic import (
OptimizationParameters,
OptimizeResponse,
@@ -40,10 +40,15 @@ def compare_dict(actual: dict[str, Any], expected: dict[str, Any]):
("optimize_input_2.json", "optimize_result_2_full.json", 400),
],
)
def test_optimize(fn_in: str, fn_out: str, ngen: int, is_full_run: bool):
def test_optimize(
fn_in: str,
fn_out: str,
ngen: int,
config_eos: ConfigEOS,
is_full_run: bool,
):
"""Test optimierung_ems."""
# Assure configuration holds the correct values
config_eos = get_config()
config_eos.merge_settings_from_dict({"prediction_hours": 48, "optimization_hours": 48})
# Load input and output data
@@ -93,3 +98,4 @@ def test_optimize(fn_in: str, fn_out: str, ngen: int, is_full_run: bool):
# The function creates a visualization result PDF as a side-effect.
prepare_visualize_patch.assert_called_once()
assert Path(visualize_filename).exists()

View File

@@ -1,43 +1,33 @@
import os
import shutil
import tempfile
from pathlib import Path
from unittest.mock import patch
import pytest
from akkudoktoreos.config.config import ConfigEOS, get_config
from akkudoktoreos.config.config import ConfigEOS
from akkudoktoreos.utils.logutil import get_logger
logger = get_logger(__name__)
config_eos = get_config()
DIR_TESTDATA = Path(__file__).absolute().parent.joinpath("testdata")
FILE_TESTDATA_CONFIGEOS_1_JSON = DIR_TESTDATA.joinpath(config_eos.CONFIG_FILE_NAME)
FILE_TESTDATA_CONFIGEOS_1_DIR = FILE_TESTDATA_CONFIGEOS_1_JSON.parent
# overwrite config_mixin fixture from conftest
@pytest.fixture(autouse=True)
def config_mixin():
pass
@pytest.fixture
def reset_config_singleton():
"""Fixture to reset the ConfigEOS singleton instance before a test."""
ConfigEOS.reset_instance()
yield
ConfigEOS.reset_instance()
def test_fixture_stash_config_file(stash_config_file, config_default_dirs):
def test_fixture_new_config_file(config_default_dirs):
"""Assure fixture stash_config_file is working."""
config_default_dir_user, config_default_dir_cwd, _ = config_default_dirs
config_default_dir_user, config_default_dir_cwd, _, _ = config_default_dirs
config_file_path_user = config_default_dir_user.joinpath(config_eos.CONFIG_FILE_NAME)
config_file_path_cwd = config_default_dir_cwd.joinpath(config_eos.CONFIG_FILE_NAME)
config_file_path_user = config_default_dir_user.joinpath(ConfigEOS.CONFIG_FILE_NAME)
config_file_path_cwd = config_default_dir_cwd.joinpath(ConfigEOS.CONFIG_FILE_NAME)
assert not config_file_path_user.exists()
assert not config_file_path_cwd.exists()
def test_config_constants():
def test_config_constants(config_eos):
"""Test config constants are the way expected by the tests."""
assert config_eos.APP_NAME == "net.akkudoktor.eos"
assert config_eos.APP_AUTHOR == "akkudoktor"
@@ -46,7 +36,7 @@ def test_config_constants():
assert config_eos.CONFIG_FILE_NAME == "EOS.config.json"
def test_computed_paths(reset_config):
def test_computed_paths(config_eos):
"""Test computed paths for output and cache."""
config_eos.merge_settings_from_dict(
{
@@ -57,56 +47,81 @@ def test_computed_paths(reset_config):
)
assert config_eos.data_output_path == Path("/base/data/output")
assert config_eos.data_cache_path == Path("/base/data/cache")
# reset settings so the config_eos fixture can verify the default paths
config_eos.reset_settings()
def test_singleton_behavior(reset_config_singleton):
def test_singleton_behavior(config_eos, config_default_dirs):
"""Test that ConfigEOS behaves as a singleton."""
instance1 = ConfigEOS()
instance2 = ConfigEOS()
initial_cfg_file = config_eos.config_file_path
with patch(
"akkudoktoreos.config.config.user_config_dir", return_value=str(config_default_dirs[0])
):
instance1 = ConfigEOS()
instance2 = ConfigEOS()
assert instance1 is config_eos
assert instance1 is instance2
assert instance1.config_file_path == initial_cfg_file
def test_default_config_path(reset_config, config_default_dirs, stash_config_file):
def test_default_config_path(config_eos, config_default_dirs):
"""Test that the default config file path is computed correctly."""
_, _, config_default_dir_default = config_default_dirs
_, _, config_default_dir_default, _ = config_default_dirs
expected_path = config_default_dir_default.joinpath("default.config.json")
assert config_eos.config_default_file_path == expected_path
assert config_eos.config_default_file_path.is_file()
def test_config_folder_path(reset_config, config_default_dirs, stash_config_file, monkeypatch):
"""Test that _config_folder_path identifies the correct config directory or None."""
config_default_dir_user, _, _ = config_default_dirs
@patch("akkudoktoreos.config.config.user_config_dir")
def test_get_config_file_path(user_config_dir_patch, config_eos, config_default_dirs, monkeypatch):
"""Test that _get_config_file_path identifies the correct config file."""
config_default_dir_user, _, _, _ = config_default_dirs
user_config_dir_patch.return_value = str(config_default_dir_user)
# All config files are stashed away, no config folder path
assert config_eos._config_folder_path() is None
def cfg_file(dir: Path) -> Path:
return dir.joinpath(ConfigEOS.CONFIG_FILE_NAME)
config_file_user = config_default_dir_user.joinpath(config_eos.CONFIG_FILE_NAME)
shutil.copy2(config_eos.config_default_file_path, config_file_user)
assert config_eos._config_folder_path() == config_default_dir_user
# Config newly created from fixture with fresh user config directory
assert config_eos._get_config_file_path() == (cfg_file(config_default_dir_user), True)
cfg_file(config_default_dir_user).unlink()
monkeypatch.setenv("EOS_DIR", str(FILE_TESTDATA_CONFIGEOS_1_DIR))
assert config_eos._config_folder_path() == FILE_TESTDATA_CONFIGEOS_1_DIR
with tempfile.TemporaryDirectory() as temp_dir:
temp_dir_path = Path(temp_dir)
monkeypatch.setenv("EOS_DIR", str(temp_dir_path))
assert config_eos._get_config_file_path() == (cfg_file(temp_dir_path), False)
# Cleanup after the test
os.remove(config_file_user)
monkeypatch.setenv("EOS_CONFIG_DIR", "config")
assert config_eos._get_config_file_path() == (
cfg_file(temp_dir_path / "config"),
False,
)
monkeypatch.setenv("EOS_CONFIG_DIR", str(temp_dir_path / "config2"))
assert config_eos._get_config_file_path() == (
cfg_file(temp_dir_path / "config2"),
False,
)
monkeypatch.delenv("EOS_DIR")
monkeypatch.setenv("EOS_CONFIG_DIR", "config3")
assert config_eos._get_config_file_path() == (cfg_file(config_default_dir_user), False)
monkeypatch.setenv("EOS_CONFIG_DIR", str(temp_dir_path / "config3"))
assert config_eos._get_config_file_path() == (
cfg_file(temp_dir_path / "config3"),
False,
)
def test_config_copy(reset_config, stash_config_file, monkeypatch):
def test_config_copy(config_eos, monkeypatch):
"""Test if the config is copied to the provided path."""
temp_dir = tempfile.TemporaryDirectory()
temp_folder_path = Path(temp_dir.name)
temp_config_file_path = temp_folder_path.joinpath(config_eos.CONFIG_FILE_NAME).resolve()
monkeypatch.setenv(config_eos.EOS_DIR, str(temp_folder_path))
if temp_config_file_path.exists():
temp_config_file_path.unlink()
assert not temp_config_file_path.exists()
assert config_eos._config_folder_path() is None
assert config_eos._config_file_path() == temp_config_file_path
config_eos.from_config_file()
assert temp_config_file_path.exists()
# Cleanup after the test
temp_dir.cleanup()
with tempfile.TemporaryDirectory() as temp_dir:
temp_folder_path = Path(temp_dir)
temp_config_file_path = temp_folder_path.joinpath(config_eos.CONFIG_FILE_NAME).resolve()
monkeypatch.setenv(config_eos.EOS_DIR, str(temp_folder_path))
assert not temp_config_file_path.exists()
with patch("akkudoktoreos.config.config.user_config_dir", return_value=temp_dir):
assert config_eos._get_config_file_path() == (temp_config_file_path, False)
config_eos.from_config_file()
assert temp_config_file_path.exists()

View File

@@ -113,7 +113,7 @@ class DerivedDataContainer(DataContainer):
class TestDataBase:
@pytest.fixture
def base(self, reset_config, monkeypatch):
def base(self):
# Provide default values for configuration
derived = DerivedBase()
derived.config.update()

View File

@@ -21,8 +21,6 @@ FILE_TESTDATA_ELECPRICEAKKUDOKTOR_1_JSON = DIR_TESTDATA.joinpath(
"elecpriceforecast_akkudoktor_1.json"
)
ems_eos = get_ems()
@pytest.fixture
def elecprice_provider(monkeypatch):
@@ -136,6 +134,7 @@ def test_update_data(mock_get, elecprice_provider, sample_akkudoktor_1_json, cac
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"))
elecprice_provider.update_data(force_enable=True, force_update=True)

View File

@@ -3,7 +3,6 @@ from pathlib import Path
import pytest
from akkudoktoreos.config.config import get_config
from akkudoktoreos.core.ems import get_ems
from akkudoktoreos.prediction.elecpriceimport import ElecPriceImport
from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime
@@ -12,12 +11,9 @@ DIR_TESTDATA = Path(__file__).absolute().parent.joinpath("testdata")
FILE_TESTDATA_ELECPRICEIMPORT_1_JSON = DIR_TESTDATA.joinpath("import_input_1.json")
config_eos = get_config()
ems_eos = get_ems()
@pytest.fixture
def elecprice_provider(reset_config, sample_import_1_json):
def elecprice_provider(sample_import_1_json, config_eos):
"""Fixture to create a ElecPriceProvider instance."""
settings = {
"elecprice_provider": "ElecPriceImport",
@@ -26,7 +22,7 @@ def elecprice_provider(reset_config, sample_import_1_json):
}
config_eos.merge_settings_from_dict(settings)
provider = ElecPriceImport()
assert provider.enabled() == True
assert provider.enabled()
return provider
@@ -49,14 +45,14 @@ def test_singleton_instance(elecprice_provider):
assert elecprice_provider is another_instance
def test_invalid_provider(elecprice_provider):
def test_invalid_provider(elecprice_provider, config_eos):
"""Test requesting an unsupported elecprice_provider."""
settings = {
"elecprice_provider": "<invalid>",
"elecpriceimport_file_path": str(FILE_TESTDATA_ELECPRICEIMPORT_1_JSON),
}
config_eos.merge_settings_from_dict(settings)
assert elecprice_provider.enabled() == False
assert not elecprice_provider.enabled()
# ------------------------------------------------
@@ -77,8 +73,9 @@ def test_invalid_provider(elecprice_provider):
("2024-10-27 00:00:00", False), # DST change in Germany (25 hours/ day)
],
)
def test_import(elecprice_provider, sample_import_1_json, start_datetime, from_file):
def test_import(elecprice_provider, sample_import_1_json, start_datetime, from_file, config_eos):
"""Test fetching forecast from Import."""
ems_eos = get_ems()
ems_eos.set_start_datetime(to_datetime(start_datetime, in_timezone="Europe/Berlin"))
if from_file:
config_eos.elecpriceimport_json = None

View File

@@ -4,7 +4,6 @@ import numpy as np
import pendulum
import pytest
from akkudoktoreos.config.config import get_config
from akkudoktoreos.core.ems import get_ems
from akkudoktoreos.measurement.measurement import MeasurementDataRecord, get_measurement
from akkudoktoreos.prediction.loadakkudoktor import (
@@ -13,12 +12,9 @@ from akkudoktoreos.prediction.loadakkudoktor import (
)
from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime, to_duration
config_eos = get_config()
ems_eos = get_ems()
@pytest.fixture
def load_provider():
def load_provider(config_eos):
"""Fixture to initialise the LoadAkkudoktor instance."""
settings = {
"load_provider": "LoadAkkudoktor",
@@ -112,6 +108,7 @@ def test_update_data(mock_load_data, load_provider):
mock_load_data.return_value = np.random.rand(365, 2, 24)
# Mock methods for updating values
ems_eos = get_ems()
ems_eos.set_start_datetime(pendulum.datetime(2024, 1, 1))
# Assure there are no prediction records

View File

@@ -1,17 +1,20 @@
import json
from pathlib import Path
from generate_openapi import generate_openapi
from unittest.mock import patch
DIR_PROJECT_ROOT = Path(__file__).parent.parent
DIR_TESTDATA = Path(__file__).parent / "testdata"
def test_openapi_spec_current():
def test_openapi_spec_current(config_eos):
"""Verify the openapi spec hasn´t changed."""
old_spec_path = DIR_PROJECT_ROOT / "docs" / "akkudoktoreos" / "openapi.json"
new_spec_path = DIR_TESTDATA / "openapi-new.json"
generate_openapi(new_spec_path)
# Patch get_config and import within guard to patch global variables within the fastapi_server module.
with patch("akkudoktoreos.config.config.get_config", return_value=config_eos):
from generate_openapi import generate_openapi
generate_openapi(new_spec_path)
with open(new_spec_path) as f_new:
new_spec = json.load(f_new)
with open(old_spec_path) as f_old:

View File

@@ -1,7 +1,6 @@
import pytest
from pydantic import ValidationError
from akkudoktoreos.config.config import get_config
from akkudoktoreos.prediction.elecpriceakkudoktor import ElecPriceAkkudoktor
from akkudoktoreos.prediction.elecpriceimport import ElecPriceImport
from akkudoktoreos.prediction.loadakkudoktor import LoadAkkudoktor
@@ -19,7 +18,7 @@ from akkudoktoreos.prediction.weatherimport import WeatherImport
@pytest.fixture
def sample_settings(reset_config):
def sample_settings(config_eos):
"""Fixture that adds settings data to the global config."""
settings = {
"prediction_hours": 48,
@@ -33,9 +32,8 @@ def sample_settings(reset_config):
}
# Merge settings to config
config = get_config()
config.merge_settings_from_dict(settings)
return config
config_eos.merge_settings_from_dict(settings)
return config_eos
@pytest.fixture

View File

@@ -7,7 +7,6 @@ import pendulum
import pytest
from pydantic import Field
from akkudoktoreos.config.config import get_config
from akkudoktoreos.core.ems import get_ems
from akkudoktoreos.prediction.prediction import PredictionCommonSettings
from akkudoktoreos.prediction.predictionabc import (
@@ -87,7 +86,7 @@ class DerivedPredictionContainer(PredictionContainer):
class TestPredictionBase:
@pytest.fixture
def base(self, reset_config, monkeypatch):
def base(self, monkeypatch):
# Provide default values for configuration
monkeypatch.setenv("latitude", "50.0")
monkeypatch.setenv("longitude", "10.0")
@@ -177,10 +176,11 @@ class TestPredictionProvider:
provider.keep_datetime == expected_keep_datetime
), "Keep datetime is not calculated correctly."
def test_update_method_with_defaults(self, provider, sample_start_datetime, monkeypatch):
def test_update_method_with_defaults(
self, provider, sample_start_datetime, config_eos, monkeypatch
):
"""Test the `update` method with default parameters."""
# EOS config supersedes
config_eos = get_config()
ems_eos = get_ems()
# The following values are currently not set in EOS config, we can override
monkeypatch.setenv("prediction_historic_hours", "2")

View File

@@ -4,7 +4,6 @@ from unittest.mock import Mock, patch
import pytest
from akkudoktoreos.config.config import get_config
from akkudoktoreos.core.ems import get_ems
from akkudoktoreos.prediction.prediction import get_prediction
from akkudoktoreos.prediction.pvforecastakkudoktor import (
@@ -22,12 +21,8 @@ FILE_TESTDATA_PV_FORECAST_INPUT_1 = DIR_TESTDATA.joinpath("pv_forecast_input_1.j
FILE_TESTDATA_PV_FORECAST_RESULT_1 = DIR_TESTDATA.joinpath("pv_forecast_result_1.txt")
config_eos = get_config()
ems_eos = get_ems()
@pytest.fixture
def sample_settings(reset_config):
def sample_settings(config_eos):
"""Fixture that adds settings data to the global config."""
settings = {
"prediction_hours": 48,
@@ -223,6 +218,7 @@ def test_pvforecast_akkudoktor_update_with_sample_forecast(
mock_get.return_value = mock_response
# Test that update properly inserts data records
ems_eos = get_ems()
ems_eos.set_start_datetime(sample_forecast_start)
provider.update_data(force_enable=True, force_update=True)
assert compare_datetimes(provider.start_datetime, sample_forecast_start).equal
@@ -230,10 +226,9 @@ def test_pvforecast_akkudoktor_update_with_sample_forecast(
# Report Generation Test
def test_report_ac_power_and_measurement(provider):
def test_report_ac_power_and_measurement(provider, config_eos):
# Set the configuration
config = get_config()
config.merge_settings_from_dict(sample_config_data)
config_eos.merge_settings_from_dict(sample_config_data)
record = PVForecastAkkudoktorDataRecord(
pvforecastakkudoktor_ac_power_measured=900.0,
@@ -275,6 +270,7 @@ def test_timezone_behaviour(
provider.clear()
assert len(provider) == 0
ems_eos = get_ems()
ems_eos.set_start_datetime(other_start_datetime)
provider.update_data(force_update=True)
assert compare_datetimes(provider.start_datetime, other_start_datetime).equal

View File

@@ -3,7 +3,6 @@ from pathlib import Path
import pytest
from akkudoktoreos.config.config import get_config
from akkudoktoreos.core.ems import get_ems
from akkudoktoreos.prediction.pvforecastimport import PVForecastImport
from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime
@@ -12,12 +11,9 @@ DIR_TESTDATA = Path(__file__).absolute().parent.joinpath("testdata")
FILE_TESTDATA_PVFORECASTIMPORT_1_JSON = DIR_TESTDATA.joinpath("import_input_1.json")
config_eos = get_config()
ems_eos = get_ems()
@pytest.fixture
def pvforecast_provider(reset_config, sample_import_1_json):
def pvforecast_provider(sample_import_1_json, config_eos):
"""Fixture to create a PVForecastProvider instance."""
settings = {
"pvforecast_provider": "PVForecastImport",
@@ -26,7 +22,7 @@ def pvforecast_provider(reset_config, sample_import_1_json):
}
config_eos.merge_settings_from_dict(settings)
provider = PVForecastImport()
assert provider.enabled() == True
assert provider.enabled()
return provider
@@ -49,14 +45,14 @@ def test_singleton_instance(pvforecast_provider):
assert pvforecast_provider is another_instance
def test_invalid_provider(pvforecast_provider):
def test_invalid_provider(pvforecast_provider, config_eos):
"""Test requesting an unsupported pvforecast_provider."""
settings = {
"pvforecast_provider": "<invalid>",
"pvforecastimport_file_path": str(FILE_TESTDATA_PVFORECASTIMPORT_1_JSON),
}
config_eos.merge_settings_from_dict(settings)
assert pvforecast_provider.enabled() == False
assert not pvforecast_provider.enabled()
# ------------------------------------------------
@@ -77,8 +73,9 @@ def test_invalid_provider(pvforecast_provider):
("2024-10-27 00:00:00", False), # DST change in Germany (25 hours/ day)
],
)
def test_import(pvforecast_provider, sample_import_1_json, start_datetime, from_file):
def test_import(pvforecast_provider, sample_import_1_json, start_datetime, from_file, config_eos):
"""Test fetching forecast from import."""
ems_eos = get_ems()
ems_eos.set_start_datetime(to_datetime(start_datetime, in_timezone="Europe/Berlin"))
if from_file:
config_eos.pvforecastimport_json = None

View File

@@ -2,12 +2,8 @@ from http import HTTPStatus
import requests
from akkudoktoreos.config.config import get_config
config_eos = get_config()
def test_server(server):
def test_server(server, config_eos):
"""Test the server."""
# validate correct path in server
assert config_eos.data_folder_path is not None

View File

@@ -1,32 +1,28 @@
import os
from pathlib import Path
from matplotlib.testing.compare import compare_images
from akkudoktoreos.config.config import get_config
from akkudoktoreos.utils.visualize import generate_example_report
filename = "example_report.pdf"
config = get_config()
output_dir = config.data_output_path
output_dir.mkdir(parents=True, exist_ok=True)
output_file = os.path.join(output_dir, filename)
DIR_TESTDATA = Path(__file__).parent / "testdata"
reference_file = DIR_TESTDATA / "test_example_report.pdf"
def test_generate_pdf_example():
def test_generate_pdf_example(config_eos):
"""Test generation of example visualization report."""
# Delete the old generated file if it exists
if os.path.isfile(output_file):
os.remove(output_file)
output_dir = config_eos.data_output_path
assert output_dir is not None
output_file = output_dir / filename
assert not output_file.exists()
generate_example_report(filename)
# Generate PDF
generate_example_report()
# Check if the file exists
assert os.path.isfile(output_file)
assert output_file.exists()
# Compare the generated file with the reference file
comparison = compare_images(str(reference_file), str(output_file), tol=0)

View File

@@ -15,8 +15,6 @@ DIR_TESTDATA = Path(__file__).absolute().parent.joinpath("testdata")
FILE_TESTDATA_WEATHERBRIGHTSKY_1_JSON = DIR_TESTDATA.joinpath("weatherforecast_brightsky_1.json")
FILE_TESTDATA_WEATHERBRIGHTSKY_2_JSON = DIR_TESTDATA.joinpath("weatherforecast_brightsky_2.json")
ems_eos = get_ems()
@pytest.fixture
def weather_provider(monkeypatch):
@@ -64,7 +62,7 @@ def test_invalid_provider(weather_provider, monkeypatch):
"""Test requesting an unsupported weather_provider."""
monkeypatch.setenv("weather_provider", "<invalid>")
weather_provider.config.update()
assert weather_provider.enabled() == False
assert not weather_provider.enabled()
def test_invalid_coordinates(weather_provider, monkeypatch):
@@ -163,6 +161,7 @@ def test_update_data(mock_get, weather_provider, sample_brightsky_1_json, cache_
cache_store.clear(clear_all=True)
# Call the method
ems_eos = get_ems()
ems_eos.set_start_datetime(to_datetime("2024-10-26 00:00:00", in_timezone="Europe/Berlin"))
weather_provider.update_data(force_enable=True, force_update=True)

View File

@@ -9,7 +9,6 @@ import pvlib
import pytest
from bs4 import BeautifulSoup
from akkudoktoreos.config.config import get_config
from akkudoktoreos.core.ems import get_ems
from akkudoktoreos.prediction.weatherclearoutside import WeatherClearOutside
from akkudoktoreos.utils.cacheutil import CacheFileStore
@@ -20,12 +19,9 @@ DIR_TESTDATA = Path(__file__).absolute().parent.joinpath("testdata")
FILE_TESTDATA_WEATHERCLEAROUTSIDE_1_HTML = DIR_TESTDATA.joinpath("weatherforecast_clearout_1.html")
FILE_TESTDATA_WEATHERCLEAROUTSIDE_1_DATA = DIR_TESTDATA.joinpath("weatherforecast_clearout_1.json")
config_eos = get_config()
ems_eos = get_ems()
@pytest.fixture
def weather_provider():
def weather_provider(config_eos):
"""Fixture to create a WeatherProvider instance."""
settings = {
"weather_provider": "ClearOutside",
@@ -70,16 +66,16 @@ def test_singleton_instance(weather_provider):
assert weather_provider is another_instance
def test_invalid_provider(weather_provider):
def test_invalid_provider(weather_provider, config_eos):
"""Test requesting an unsupported weather_provider."""
settings = {
"weather_provider": "<invalid>",
}
config_eos.merge_settings_from_dict(settings)
assert weather_provider.enabled() == False
assert not weather_provider.enabled()
def test_invalid_coordinates(weather_provider):
def test_invalid_coordinates(weather_provider, config_eos):
"""Test invalid coordinates raise ValueError."""
settings = {
"weather_provider": "ClearOutside",
@@ -118,7 +114,7 @@ def test_irridiance_estimate_from_cloud_cover(weather_provider):
@patch("requests.get")
def test_request_forecast(mock_get, weather_provider, sample_clearout_1_html):
def test_request_forecast(mock_get, weather_provider, sample_clearout_1_html, config_eos):
"""Test fetching forecast from ClearOutside."""
# Mock response object
mock_response = Mock()
@@ -149,6 +145,7 @@ def test_update_data(mock_get, weather_provider, sample_clearout_1_html, sample_
expected_keep = to_datetime("2024-10-24 00:00:00", in_timezone="Europe/Berlin")
# Call the method
ems_eos = get_ems()
ems_eos.set_start_datetime(expected_start)
weather_provider.update_data()

View File

@@ -3,7 +3,6 @@ from pathlib import Path
import pytest
from akkudoktoreos.config.config import get_config
from akkudoktoreos.core.ems import get_ems
from akkudoktoreos.prediction.weatherimport import WeatherImport
from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime
@@ -12,12 +11,9 @@ DIR_TESTDATA = Path(__file__).absolute().parent.joinpath("testdata")
FILE_TESTDATA_WEATHERIMPORT_1_JSON = DIR_TESTDATA.joinpath("import_input_1.json")
config_eos = get_config()
ems_eos = get_ems()
@pytest.fixture
def weather_provider(reset_config, sample_import_1_json):
def weather_provider(sample_import_1_json, config_eos):
"""Fixture to create a WeatherProvider instance."""
settings = {
"weather_provider": "WeatherImport",
@@ -49,7 +45,7 @@ def test_singleton_instance(weather_provider):
assert weather_provider is another_instance
def test_invalid_provider(weather_provider, monkeypatch):
def test_invalid_provider(weather_provider, config_eos, monkeypatch):
"""Test requesting an unsupported weather_provider."""
settings = {
"weather_provider": "<invalid>",
@@ -77,8 +73,9 @@ def test_invalid_provider(weather_provider, monkeypatch):
("2024-10-27 00:00:00", False), # DST change in Germany (25 hours/ day)
],
)
def test_import(weather_provider, sample_import_1_json, start_datetime, from_file):
def test_import(weather_provider, sample_import_1_json, start_datetime, from_file, config_eos):
"""Test fetching forecast from Import."""
ems_eos = get_ems()
ems_eos.set_start_datetime(to_datetime(start_datetime, in_timezone="Europe/Berlin"))
if from_file:
config_eos.weatherimport_json = None