mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2025-09-13 07:21:16 +00:00
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:
committed by
GitHub
parent
267a9bf427
commit
75987db9e1
@@ -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"
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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()
|
||||
|
@@ -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()
|
||||
|
@@ -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()
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
|
@@ -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")
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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()
|
||||
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user