mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-11-13 09:13:30 +01:00
201 lines
8.0 KiB
Python
201 lines
8.0 KiB
Python
"""Tests for the EOSdash settings in the EOS configuration (eosdash/plan)."""
|
|||
|
|
|
||
|
|
from unittest.mock import MagicMock, patch
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from pydantic import ValidationError
|
||
|
|
|
||
|
|
import akkudoktoreos.server.dash.eosstatus as eosstatus
|
||
|
|
from akkudoktoreos.config.config import SettingsEOS
|
||
|
|
from akkudoktoreos.server.dash import plan
|
||
|
|
from akkudoktoreos.server.dash.dashsettings import (
|
||
|
|
EOSdashCommonSettings,
|
||
|
|
EOSdashPlanSettings,
|
||
|
|
)
|
||
|
|
|
||
|
|
SERVER = "http://127.0.0.1:8503"
|
||
|
|
|
||
|
|
|
||
|
|
class TestEOSdashPlanSettings:
|
||
|
|
def test_defaults(self):
|
||
|
|
settings = EOSdashPlanSettings()
|
||
|
|
assert settings.visible == {}
|
||
|
|
assert settings.color == {}
|
||
|
|
assert settings.device_alias == {}
|
||
|
|
assert settings.plan_table_open is False
|
||
|
|
assert settings.energy_kwh is True
|
||
|
|
assert settings.factor_percent is True
|
||
|
|
assert settings.price_minor_unit is True
|
||
|
|
assert EOSdashCommonSettings().plan.model_dump() == settings.model_dump()
|
||
|
|
|
||
|
|
def test_valid_values(self):
|
||
|
|
settings = EOSdashPlanSettings(
|
||
|
|
color={"battery1_soc_factor": "#DAA520"},
|
||
|
|
style={"pvforecast_ac_energy_wh": "bar"},
|
||
|
|
width={"loadforecast_energy_wh": 5},
|
||
|
|
line_dash={"mode_SELF_CONSUMPTION": "dotted"},
|
||
|
|
alias={"mode_SELF_CONSUMPTION": " Self consumption "},
|
||
|
|
)
|
||
|
|
assert settings.alias["mode_SELF_CONSUMPTION"] == "Self consumption"
|
||
|
|
|
||
|
|
@pytest.mark.parametrize(
|
||
|
|
"field, value",
|
||
|
|
[
|
||
|
|
("color", {"col": "red"}),
|
||
|
|
("color", {"col": "#12345"}),
|
||
|
|
("style", {"col": "area"}),
|
||
|
|
("width", {"col": 0}),
|
||
|
|
("width", {"col": 6}),
|
||
|
|
("line_dash", {"col": "dashdot"}),
|
||
|
|
("alias", {"col": ""}),
|
||
|
|
("alias", {"col": "x" * 33}),
|
||
|
|
("device_alias", {"battery1": " "}),
|
||
|
|
],
|
||
|
|
)
|
||
|
|
def test_invalid_values(self, field, value):
|
||
|
|
with pytest.raises(ValidationError):
|
||
|
|
EOSdashPlanSettings(**{field: value})
|
||
|
|
|
||
|
|
|
||
|
|
class TestEOSdashConfig:
|
||
|
|
def test_config_has_eosdash_section(self, config_eos):
|
||
|
|
assert config_eos.eosdash.plan.model_dump() == EOSdashPlanSettings().model_dump()
|
||
|
|
|
||
|
|
def test_set_nested_value(self, config_eos):
|
||
|
|
config_eos.set_nested_value("eosdash/plan/color", {"battery1_soc_factor": "#FF0000"})
|
||
|
|
config_eos.set_nested_value("eosdash/plan/price_minor_unit", False)
|
||
|
|
assert config_eos.eosdash.plan.color == {"battery1_soc_factor": "#FF0000"}
|
||
|
|
assert config_eos.eosdash.plan.price_minor_unit is False
|
||
|
|
with pytest.raises(ValueError):
|
||
|
|
config_eos.set_nested_value("eosdash/plan/color", {"battery1_soc_factor": "red"})
|
||
|
|
|
||
|
|
def test_merge_settings_from_dict(self, config_eos):
|
||
|
|
config_eos.merge_settings_from_dict(
|
||
|
|
{"eosdash": {"plan": {"device_alias": {"battery1": "House battery"}}}}
|
||
|
|
)
|
||
|
|
assert config_eos.eosdash.plan.device_alias == {"battery1": "House battery"}
|
||
|
|
# Other plan settings keep their defaults
|
||
|
|
assert config_eos.eosdash.plan.energy_kwh is True
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
def eosdash_config():
|
||
|
|
"""EOSdash copy of the EOS configuration, restored after the test."""
|
||
|
|
saved = eosstatus.eos_config
|
||
|
|
eosstatus.eos_config = SettingsEOS(
|
||
|
|
eosdash=EOSdashCommonSettings(
|
||
|
|
plan=EOSdashPlanSettings(
|
||
|
|
color={"battery1_soc_factor": "#00FF00"},
|
||
|
|
alias={"mode_IDLE": "Idle"},
|
||
|
|
device_alias={"battery1": "House battery"},
|
||
|
|
price_minor_unit=False,
|
||
|
|
)
|
||
|
|
)
|
||
|
|
)
|
||
|
|
yield eosstatus.eos_config
|
||
|
|
eosstatus.eos_config = saved
|
||
|
|
|
||
|
|
|
||
|
|
def put_response(config: SettingsEOS) -> MagicMock:
|
||
|
|
"""Response of EOS to a configuration update."""
|
||
|
|
response = MagicMock()
|
||
|
|
response.json.return_value = config.model_dump(mode="json")
|
||
|
|
return response
|
||
|
|
|
||
|
|
|
||
|
|
class TestPlanSettings:
|
||
|
|
def test_defaults_without_config(self):
|
||
|
|
saved = eosstatus.eos_config
|
||
|
|
eosstatus.eos_config = None
|
||
|
|
try:
|
||
|
|
assert plan.plan_settings().model_dump() == EOSdashPlanSettings().model_dump()
|
||
|
|
assert plan.plan_option("price_minor_unit") is True
|
||
|
|
finally:
|
||
|
|
eosstatus.eos_config = saved
|
||
|
|
|
||
|
|
def test_settings_from_config(self, eosdash_config):
|
||
|
|
assert plan.solution_column_color("battery1_soc_factor") == "#00FF00"
|
||
|
|
assert plan.plan_mode_label("IDLE") == "Idle"
|
||
|
|
assert plan.device_alias("battery1") == "House battery"
|
||
|
|
assert plan.device_alias("ev11") == "ev11"
|
||
|
|
assert plan.column_label("battery1_soc_factor", ["battery1"]) == "House battery soc_factor"
|
||
|
|
assert plan.plan_option("price_minor_unit") is False
|
||
|
|
assert plan.plan_option("energy_kwh") is True
|
||
|
|
|
||
|
|
def test_put_replaces_config(self, eosdash_config):
|
||
|
|
updated = eosdash_config.model_copy(deep=True)
|
||
|
|
updated.eosdash.plan.price_minor_unit = True
|
||
|
|
with patch.object(plan.requests, "put", return_value=put_response(updated)) as put:
|
||
|
|
plan.plan_settings_put(SERVER, "price_minor_unit", True)
|
||
|
|
put.assert_called_once_with(
|
||
|
|
f"{SERVER}/v1/config/eosdash/plan/price_minor_unit", json=True, timeout=10
|
||
|
|
)
|
||
|
|
assert plan.plan_option("price_minor_unit") is True
|
||
|
|
|
||
|
|
def test_put_failure_keeps_config(self, eosdash_config):
|
||
|
|
with patch.object(plan.requests, "put", side_effect=OSError("no connection")):
|
||
|
|
plan.plan_settings_put(SERVER, "price_minor_unit", True)
|
||
|
|
assert plan.plan_option("price_minor_unit") is False
|
||
|
|
|
||
|
|
@pytest.mark.parametrize(
|
||
|
|
"data, key, value",
|
||
|
|
[
|
||
|
|
(
|
||
|
|
{"action": "color", "renderer": "load", "color": "#abcdef"},
|
||
|
|
"color",
|
||
|
|
{"battery1_soc_factor": "#00FF00", "load": "#ABCDEF"},
|
||
|
|
),
|
||
|
|
({"action": "reset", "renderer": "battery1_soc_factor"}, "color", {}),
|
||
|
|
({"action": "visible", "renderer": "load", "load-visible": "true"}, "visible", {"load": True}),
|
||
|
|
({"action": "visible", "renderer": "load"}, "visible", {"load": False}),
|
||
|
|
({"action": "style", "renderer": "load", "style": "bar"}, "style", {"load": "bar"}),
|
||
|
|
({"action": "width", "renderer": "load", "width": "3"}, "width", {"load": 3}),
|
||
|
|
({"action": "dash", "renderer": "load", "dash": "dotted"}, "line_dash", {"load": "dotted"}),
|
||
|
|
(
|
||
|
|
{"action": "alias", "renderer": "load", "alias": " House load\x00 "},
|
||
|
|
"alias",
|
||
|
|
{"mode_IDLE": "Idle", "load": "House load"},
|
||
|
|
),
|
||
|
|
({"action": "alias", "renderer": "mode_IDLE", "alias": ""}, "alias", {}),
|
||
|
|
],
|
||
|
|
)
|
||
|
|
def test_solution_settings_action(self, eosdash_config, data, key, value):
|
||
|
|
with patch.object(plan, "plan_settings_put") as put:
|
||
|
|
plan.solution_settings_action(SERVER, data)
|
||
|
|
put.assert_called_once_with(SERVER, key, value)
|
||
|
|
|
||
|
|
@pytest.mark.parametrize(
|
||
|
|
"data",
|
||
|
|
[
|
||
|
|
{"action": "color", "renderer": "load", "color": "red"},
|
||
|
|
{"action": "style", "renderer": "load", "style": "area"},
|
||
|
|
{"action": "width", "renderer": "load", "width": "9"},
|
||
|
|
{"action": "dash", "renderer": "load", "dash": "dashdot"},
|
||
|
|
{"action": "color", "color": "#ABCDEF"},
|
||
|
|
],
|
||
|
|
)
|
||
|
|
def test_solution_settings_action_invalid(self, eosdash_config, data):
|
||
|
|
with patch.object(plan, "plan_settings_put") as put:
|
||
|
|
plan.solution_settings_action(SERVER, data)
|
||
|
|
put.assert_not_called()
|
||
|
|
|
||
|
|
|
||
|
|
class TestCurrencyMinorUnit:
|
||
|
|
@pytest.mark.parametrize(
|
||
|
|
"currency, expected",
|
||
|
|
[
|
||
|
|
("EUR", ("ct", 100)),
|
||
|
|
("CHF", ("Rp.", 100)),
|
||
|
|
("SEK", ("1/100 SEK", 100)),
|
||
|
|
("KWD", ("1/1000 KWD", 1000)),
|
||
|
|
("JPY", None),
|
||
|
|
(None, None),
|
||
|
|
],
|
||
|
|
)
|
||
|
|
def test_currency_minor_unit(self, currency, expected):
|
||
|
|
assert plan.currency_minor_unit(currency) == expected
|
||
|
|
|
||
|
|
def test_config_currency(self):
|
||
|
|
config = SettingsEOS.model_validate({"general": {"timezone_override": "Europe/Zurich"}})
|
||
|
|
assert plan.config_currency(config) == "CHF"
|