Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/predbat/predbat.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
import pytz
import asyncio

THIS_VERSION = "v8.48.4"
THIS_VERSION = "v8.48.5"

from download import predbat_update_move, predbat_update_download, check_install, DEFAULT_PREDBAT_REPOSITORY
from const import MINUTE_WATT
Expand Down
1 change: 1 addition & 0 deletions apps/predbat/solis.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ def initialize(self, api_key=None, api_secret=None, inverter_sn=None, automatic=

# Tracking
self.slots_reset = set() # Track which inverters had slots reset
self.capacity_voltage_warned = set() # Inverters already warned about an estimated capacity voltage

self.log(f"Solis API: Initialised with inverter_sn={self.configured_inverter_sn}, automatic={automatic}")

Expand Down
26 changes: 25 additions & 1 deletion apps/predbat/tests/test_solis.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ def __init__(self, prefix="predbat"):
# Cache structures
self.cached_values = {}
self.inverter_details = {}
self.storage_modes = {}
self.parallel_battery_count = {}
self.max_charge_current = {}
self.max_discharge_current = {}
Expand Down Expand Up @@ -778,6 +777,30 @@ def test_solis_activated_with_api_key():
return failed


def test_initialize_attribute_parity_with_mock():
"""Every attribute MockSolisAPI stubs must also be set by the real SolisAPI.initialize().

MockSolisAPI replaces __init__ wholesale, so an attribute added to the mock but forgotten in
the real initialize() passes every unit test here and then raises AttributeError against a
live inverter — which is exactly how capacity_voltage_warned shipped broken in #4502.
"""
failed = False
# Attributes that exist only to drive the test harness and have no production counterpart.
test_only = {"_test_now_utc_exact", "log_messages", "dashboard_items", "read_and_write_cid_calls", "set_storage_mode_calls"}
component = _init_solis_component({"solis_api_key": "k", "solis_api_secret": "s"})
if component is None:
print("ERROR: Solis should activate with api_key + api_secret")
return True
mock_attrs = set(vars(MockSolisAPI()).keys()) - test_only
missing = sorted(attr for attr in mock_attrs if not hasattr(component, attr))
if missing:
print("ERROR: SolisAPI.initialize() does not set attributes stubbed by MockSolisAPI: {}".format(missing))
failed = True
if not failed:
print("PASSED: SolisAPI.initialize() sets every attribute MockSolisAPI stubs")
return failed


def test_solis_activated_with_oauth_token():
"""Solis activates in OAuth mode when auth_method=oauth + access_token are configured (no api_key)."""
failed = False
Expand Down Expand Up @@ -1018,6 +1041,7 @@ def run_solis_tests(my_predbat):
failed |= test_solis_not_activated_without_credentials()
failed |= test_solis_activated_with_api_key()
failed |= test_solis_activated_with_oauth_token()
failed |= test_initialize_attribute_parity_with_mock()
failed |= asyncio.run(test_oauth_execute_request_refreshes_before_call())
failed |= asyncio.run(test_oauth_endpoint_namespace_translation())
failed |= asyncio.run(test_oauth_execute_request_aborts_when_token_missing())
Expand Down
Loading