Skip to content

Commit

Permalink
test: add new tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Hoffmann77 authored Oct 14, 2023
1 parent af9d062 commit 692b642
Show file tree
Hide file tree
Showing 149 changed files with 803 additions and 95 deletions.
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ jobs:
- name: Test with pytest
run: |
pytest
# pytest --log-cli-level=DEBUG
31 changes: 31 additions & 0 deletions custom_components/enphase_gateway/gateway_reader/descriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,37 @@ def __set_name__(self, owner, name) -> None:
setattr(owner, uid, {name: _endpoint})


class PropertyDescriptor(BaseDescriptor):
"""Property descriptor.
A pure python implementation of property that registers the
required endpoint and the caching interval.
"""

def __init__(
self,
fget=None,
doc=None,
required_endpoint: str | None = None,
cache: int = 0,
) -> None:
"""Initialize instance of PropertyDescriptor."""
super().__init__(required_endpoint, cache)
self.fget = fget
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
self._name = ""

def __get__(self, obj, objtype=None):
"""Magic method. Return the response of the fget function."""
if obj is None:
return self
if self.fget is None:
raise AttributeError(f"property '{self._name}' has no getter")
return self.fget(obj)


class ResponseDescriptor(BaseDescriptor):
"""Descriptor returning the raw response."""

Expand Down
16 changes: 12 additions & 4 deletions custom_components/enphase_gateway/gateway_reader/endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@
class GatewayEndpoint:
"""Class representing a Gateway endpoint."""

def __init__(self, endpoint_path: str, cache: int = 0) -> None:
def __init__(
self,
endpoint_path: str,
cache: int = 0,
fetch: bool = True
) -> None:
"""Initialize instance of GatewayEndpoint."""
self.path = endpoint_path
self.cache = cache
self.fetch = fetch
self._last_fetch = None
self._base_url = "{}://{}/{}"

Expand All @@ -20,12 +26,14 @@ def __repr__(self):
@property
def update_required(self) -> bool:
"""Check if an update is required for this endpoint."""
if not self._last_fetch:
if self.fetch is False:
return False
elif not self._last_fetch:
return True
elif (self._last_fetch + self.cache) <= time.time():
return True
else:
return False

return False

def get_url(self, protocol, host):
"""Return formatted url."""
Expand Down
79 changes: 50 additions & 29 deletions custom_components/enphase_gateway/gateway_reader/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,50 @@

from .const import AVAILABLE_PROPERTIES
from .endpoint import GatewayEndpoint
from .descriptors import ResponseDescriptor, JsonDescriptor, RegexDescriptor
from .descriptors import (
PropertyDescriptor,
ResponseDescriptor,
JsonDescriptor,
RegexDescriptor,
)


_LOGGER = logging.getLogger(__name__)


def gateway_property(_func: Callable | None = None, **kwargs) -> None:
"""Register an instance's method as a property of a gateway.
def gateway_property(
_func: Callable | None = None,
**kwargs: dict,
) -> PropertyDescriptor:
"""Decorate the given method as gateway property.
Works identical to the python property decorator.
Additionally registers the method to the '_gateway_properties' dict
of the methods parent class.
Parameters
----------
_func : Callable, optional
Decorated method. The default is None.
**kwargs
Method to decorate. The default is None.
**kwargs : dict
Optional keyword arguments.
Returns
-------
method
Decorated method.
PropertyDescriptor
Property descriptor calling the method on attribute access.
"""
required_endpoint = kwargs.pop("required_endpoint", None)
cache = kwargs.pop("cache", 0)

def decorator(func):
endpoint = None
if required_endpoint:
endpoint = GatewayEndpoint(required_endpoint, cache)

func.gateway_property = endpoint # flag method as gateway property
return func
return PropertyDescriptor(
fget=func,
doc=None,
required_endpoint=required_endpoint,
cache=cache,
)

return decorator if _func is None else decorator(_func)

Expand Down Expand Up @@ -120,15 +132,7 @@ def __new__(cls, *args, **kwargs):

# catch flagged methods and add to instance's
# _gateway_properties or _gateway_probes.
if endpoint := getattr(attr_val, "gateway_property", None):
if attr_name not in gateway_properties.keys():
gateway_properties[attr_name] = endpoint
setattr(
instance.__class__,
attr_name,
property(attr_val),
)
elif endpoint := getattr(attr_val, "gateway_probe", None):
if endpoint := getattr(attr_val, "gateway_probe", None):
gateway_probes.setdefault(attr_name, endpoint)

instance._gateway_properties = gateway_properties
Expand Down Expand Up @@ -185,12 +189,15 @@ def update_endpoints(endpoint):
for prop, prop_endpoint in self._gateway_properties.items():
if isinstance(prop_endpoint, GatewayEndpoint):

value = getattr(self, prop)
if self.initial_update_finished and value in (None, [], {}):
if self.initial_update_finished:
# When the value is None or empty list or dict,
# then the endpoint is useless for this token,
# so do not require it.
continue
# so we do not require it.
if (val := getattr(self, prop)) in (None, [], {}):
_LOGGER.debug(
f"Skip property: {prop} : {prop_endpoint} : {val}"
)
continue

update_endpoints(prop_endpoint)

Expand Down Expand Up @@ -233,6 +240,9 @@ def set_endpoint_data(
return

content_type = response.headers.get("content-type", "application/json")
_LOGGER.debug(
f"Setting endpoint data: {endpoint} : {response.content}"
)
if content_type == "application/json":
self.data[endpoint.path] = response.json()
elif content_type in ("text/xml", "application/xml"):
Expand Down Expand Up @@ -381,12 +391,23 @@ def encharge_power(self):

return None

# @gateway_property
# def ac_battery(self) -> ACBattery | None:
# @gateway_property(required_endpoint="ivp/ensemble/secctrl")
# def ensemble_secctrl(self):
# """Ensemble secctrl data."""
# data = self.data.get("ivp/ensemble/secctrl", {})
# result = JsonDescriptor.resolve("", data)
# if self.initial_update_finished is False:
# if self.encharge_inventory is None:
# return None

# return result if result else None

# @gateway_property(required_endpoint="production.json")
# def ac_battery(self):
# """AC battery data."""
# data = self.data.get("production.json", {})
# result = JsonDescriptor.resolve("storage[?(@.percentFull)]", data)
# return ACBattery(result) if result else None
# return result if result else None

# @gateway_property(required_endpoint="ensemble_submod")
# def ensemble_submod(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ async def prepare(self):
+ f"imeter: {self._info.imeter}, "
+ f"web_tokens: {self._info.web_tokens}"
)
_LOGGER.debug(f"Initial Gateway class: {self.gateway.__class__}")
_LOGGER.debug(
f"Initial Gateway class: {self.gateway.__class__.__name__}"
)

async def update(
self,
Expand Down Expand Up @@ -227,7 +229,6 @@ async def _update_endpoint(self, endpoint: GatewayEndpoint) -> None:
follow_redirects=False
)
if self.gateway:
_LOGGER.debug(f"Setting endpoint data: {endpoint} : {response}")
self.gateway.set_endpoint_data(endpoint, response)

async def _async_get(self, url: str, handle_401: bool = True, **kwargs):
Expand Down
2 changes: 1 addition & 1 deletion custom_components/enphase_gateway/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ async def async_setup_entry(
GatewaySensorEntity(coordinator, sensor_description)
)

if data := coordinator.data.inverters_production and conf_inverters:
if (data := coordinator.data.inverters_production) and conf_inverters:
if conf_inverters == "gateway_sensor":
entities.extend(
GatewaySensorInverterEntity(coordinator, description, inverter)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"date": "Sun, 03 Sep 2023 12:45:33 GMT",
"content-length": "457",
"connection": "keep-alive",
"content-type": "application/json",
"strict-transport-security": "max-age=63072000; includeSubdomains",
"x-frame-options": "DENY",
"x-content-type-options": "nosniff"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"headers": {
"server": "openresty/1.17.8.1",
"date": "Sun, 03 Sep 2023 12:45:33 GMT",
"content-length": "4843",
"connection": "keep-alive",
"strict-transport-security": "max-age=63072000; includeSubdomains",
"x-frame-options": "DENY",
"x-content-type-options": "nosniff"
},
"code": 200
}
16 changes: 16 additions & 0 deletions tests/fixtures/7.6.175_envoy_s_metered/production.json_log.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"headers": {
"server": "openresty/1.17.8.1",
"date": "Sun, 03 Sep 2023 12:45:29 GMT",
"content-type": "application/json",
"transfer-encoding": "chunked",
"connection": "keep-alive",
"pragma": "no-cache",
"expires": "1",
"cache-control": "no-cache",
"strict-transport-security": "max-age=63072000; includeSubdomains",
"x-frame-options": "DENY",
"x-content-type-options": "nosniff"
},
"code": 200
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"tariff":{"currency":{"code":"EUR"},"logger":"mylogger","date":"1688623885","storage_settings":{"mode":"self-consumption","operation_mode_sub_type":"","reserved_soc":30.0,"very_low_soc":10,"charge_from_grid":false,"date":"1688623885"},"single_rate":{"rate":0.38914,"sell":0.0},"seasons":[{"id":"all_year_long","start":"1/1","days":[{"id":"all_days","days":"Mon,Tue,Wed,Thu,Fri,Sat,Sun","must_charge_start":0,"must_charge_duration":0,"must_charge_mode":"CG","enable_discharge_to_grid":false,"periods":[{"id":"filler","start":0,"rate":0.38914},{"id":"period_1","start":420,"rate":0.4424},{"id":"filler","start":1381,"rate":0.38914}]}],"tiers":[]}],"seasons_sell":[]},"schedule":{"source":"Tariff","date":"2023-07-06 06:11:26 UTC","version":"00.00.02","reserved_soc":30.0,"operation_mode_sub_type":"","very_low_soc":10,"charge_from_grid":false,"battery_mode":"self-consumption","schedule":{"Disable":[{"Sun":[{"start":0,"duration":1440,"setting":"ID"}]},{"Mon":[{"start":0,"duration":1440,"setting":"ID"}]},{"Tue":[{"start":0,"duration":1440,"setting":"ID"}]},{"Wed":[{"start":0,"duration":1440,"setting":"ID"}]},{"Thu":[{"start":0,"duration":1440,"setting":"ID"}]},{"Fri":[{"start":0,"duration":1440,"setting":"ID"}]},{"Sat":[{"start":0,"duration":1440,"setting":"ID"}]}],"tariff":[{"start":"1/1","end":"1/1","Sun":[{"start":0,"duration":1440,"setting":"ZN"}],"Mon":[{"start":0,"duration":1440,"setting":"ZN"}],"Tue":[{"start":0,"duration":1440,"setting":"ZN"}],"Wed":[{"start":0,"duration":1440,"setting":"ZN"}],"Thu":[{"start":0,"duration":1440,"setting":"ZN"}],"Fri":[{"start":0,"duration":1440,"setting":"ZN"}],"Sat":[{"start":0,"duration":1440,"setting":"ZN"}]}]},"override":false,"override_backup_soc":30.0,"override_chg_dischg_rate":0.0,"override_tou_mode":"StorageTouMode_DEFAULT_TOU_MODE"}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"headers": {"server": "openresty/1.17.8.1", "date": "Fri, 08 Sep 2023 14:12:46 GMT", "content-type": "application/json", "transfer-encoding": "chunked", "connection": "keep-alive", "pragma": "no-cache", "expires": "1", "cache-control": "no-cache", "strict-transport-security": "max-age=63072000; includeSubdomains", "x-frame-options": "DENY", "x-content-type-options": "nosniff"}, "code": 200}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"wattHoursToday": 0,
"wattHoursSevenDays": 0,
"wattHoursLifetime": 0,
"wattsNow": 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
[
{
"serialNumber": "122107031694",
"lastReportDate": 1694182111,
"devType": 1,
"lastReportWatts": 193,
"maxReportWatts": 289
},
{
"serialNumber": "122107031731",
"lastReportDate": 1694182200,
"devType": 1,
"lastReportWatts": 22,
"maxReportWatts": 296
},
{
"serialNumber": "122107033673",
"lastReportDate": 1694182140,
"devType": 1,
"lastReportWatts": 21,
"maxReportWatts": 296
},
{
"serialNumber": "122107033629",
"lastReportDate": 1694182081,
"devType": 1,
"lastReportWatts": 194,
"maxReportWatts": 289
},
{
"serialNumber": "122107035554",
"lastReportDate": 1694182170,
"devType": 1,
"lastReportWatts": 21,
"maxReportWatts": 296
},
{
"serialNumber": "122107033602",
"lastReportDate": 1694181992,
"devType": 1,
"lastReportWatts": 22,
"maxReportWatts": 296
},
{
"serialNumber": "122107035597",
"lastReportDate": 1694182052,
"devType": 1,
"lastReportWatts": 21,
"maxReportWatts": 296
},
{
"serialNumber": "122107032623",
"lastReportDate": 1694181960,
"devType": 1,
"lastReportWatts": 190,
"maxReportWatts": 279
},
{
"serialNumber": "122107035544",
"lastReportDate": 1694182140,
"devType": 1,
"lastReportWatts": 191,
"maxReportWatts": 283
},
{
"serialNumber": "122107035551",
"lastReportDate": 1694182111,
"devType": 1,
"lastReportWatts": 191,
"maxReportWatts": 287
},
{
"serialNumber": "122107032918",
"lastReportDate": 1694181930,
"devType": 1,
"lastReportWatts": 21,
"maxReportWatts": 296
},
{
"serialNumber": "122107032940",
"lastReportDate": 1694182172,
"devType": 1,
"lastReportWatts": 21,
"maxReportWatts": 296
},
{
"serialNumber": "122107025358",
"lastReportDate": 1694181961,
"devType": 1,
"lastReportWatts": 190,
"maxReportWatts": 279
},
{
"serialNumber": "122107032484",
"lastReportDate": 1694182142,
"devType": 1,
"lastReportWatts": 21,
"maxReportWatts": 296
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"headers": {"server": "openresty/1.17.8.1", "date": "Fri, 08 Sep 2023 14:10:45 GMT", "content-type": "application/json", "content-length": "2109", "connection": "keep-alive", "strict-transport-security": "max-age=63072000; includeSubdomains", "x-frame-options": "DENY", "x-content-type-options": "nosniff"}, "code": 200}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"headers": {"server": "openresty/1.17.8.1", "date": "Fri, 08 Sep 2023 14:10:45 GMT", "content-type": "application/json", "content-length": "96", "connection": "keep-alive", "strict-transport-security": "max-age=63072000; includeSubdomains", "x-frame-options": "DENY", "x-content-type-options": "nosniff"}, "code": 200}
Loading

0 comments on commit 692b642

Please sign in to comment.