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
8 changes: 8 additions & 0 deletions openevsehttp/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1379,3 +1379,11 @@ async def async_override_state(self) -> str | None:
if "state" in override.keys():
return override["state"]
return "auto"

@property
def current_power(self) -> int:
"""Return the current power (live) in watts."""
if not self._version_check("4.2.2"):
_LOGGER.debug("Feature not supported for older firmware.")
raise UnsupportedFeature
return self._status.get("power", 0)
2 changes: 1 addition & 1 deletion tests/fixtures/v4_json/status-new.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"evse_connected": 1,
"amp": 0,
"voltage": 220,
"power": 0,
"power": 4500,
"pilot": 48,
"max_current": 48,
"temp": 440,
Expand Down
31 changes: 28 additions & 3 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2215,7 +2215,7 @@ async def test_set_divert_mode(


async def test_main_auth_instantiation():
"""Test OpenEVSE auth instantiation (covers __main__.py:111-113)."""
"""Test OpenEVSE auth instantiation."""
charger = OpenEVSE(SERVER_URL, user="user", pwd="password")

# Setup mock session to be an async context manager
Expand Down Expand Up @@ -2247,7 +2247,7 @@ async def test_main_auth_instantiation():


async def test_main_sync_callback():
"""Test synchronous callback in _update_status (covers __main__.py:293)."""
"""Test synchronous callback in _update_status."""
charger = OpenEVSE(SERVER_URL)
sync_callback = MagicMock()
charger.callback = sync_callback
Expand All @@ -2259,7 +2259,7 @@ async def test_main_sync_callback():


async def test_send_command_msg_fallback():
"""Test send_command return logic fallback (covers __main__.py:181)."""
"""Test send_command return logic fallback."""
charger = OpenEVSE(SERVER_URL)

# Mock response with 'msg' but no 'ret'
Expand All @@ -2278,3 +2278,28 @@ async def test_send_command_empty_fallback():
cmd, ret = await charger.send_command("$ST")
assert cmd is False
assert ret == ""


@pytest.mark.parametrize(
"fixture, expected",
[
("test_charger", UnsupportedFeature),
("test_charger_v2", UnsupportedFeature),
("test_charger_broken", UnsupportedFeature),
("test_charger_new", 4500),
],
)
async def test_power(fixture, expected, request):
"""Test current_power property."""
charger = request.getfixturevalue(fixture)
await charger.update()

# If we expect an exception (UnsupportedFeature), we must use pytest.raises
if expected is UnsupportedFeature:
with pytest.raises(UnsupportedFeature):
_ = charger.current_power
else:
# Otherwise, we check the returned value
assert charger.current_power == expected

await charger.ws_disconnect()
12 changes: 6 additions & 6 deletions tests/test_websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ async def ws_client_auth():

@pytest.mark.asyncio
async def test_websocket_auth(ws_client_auth):
"""Test WebSocket connection with authentication (covers websocket.py:72-73)."""
"""Test WebSocket connection with authentication."""
mock_ws = MagicMock()
mock_ws.__aenter__ = AsyncMock(return_value=mock_ws)
mock_ws.__aexit__ = AsyncMock(return_value=None)
Expand All @@ -189,7 +189,7 @@ async def empty_iter():

@pytest.mark.asyncio
async def test_websocket_message_types(ws_client_auth):
"""Test CLOSED and ERROR message types (covers websocket.py:93-101)."""
"""Test CLOSED and ERROR message types."""
# 1. Test CLOSED message
msg_closed = MagicMock()
msg_closed.type = aiohttp.WSMsgType.CLOSED
Expand Down Expand Up @@ -223,15 +223,15 @@ async def async_iter_error():

@pytest.mark.asyncio
async def test_websocket_exceptions_generic(ws_client_auth):
"""Test generic exception during run (covers websocket.py:125-129)."""
"""Test generic exception during run."""
with patch("aiohttp.ClientSession.ws_connect", side_effect=Exception("Boom")):
await ws_client_auth.running()
assert ws_client_auth.state == STATE_STOPPED


@pytest.mark.asyncio
async def test_websocket_unexpected_response_error(ws_client_auth):
"""Test unexpected client response error (covers websocket.py:108-109)."""
"""Test unexpected client response error."""
# Status 500 triggers the "else" block in the exception handler
error = aiohttp.ClientResponseError(
request_info=MagicMock(), history=MagicMock(), status=500
Expand All @@ -249,15 +249,15 @@ async def test_websocket_unexpected_response_error(ws_client_auth):

@pytest.mark.asyncio
async def test_keepalive_client_missing(ws_client_auth):
"""Test keepalive when client is None (covers websocket.py:139)."""
"""Test keepalive when client is None."""
ws_client_auth._client = None
# Should log warning but not crash
await ws_client_auth.keepalive()


@pytest.mark.asyncio
async def test_keepalive_send_exceptions(ws_client_auth):
"""Test exceptions during keepalive send (covers websocket.py:164-174)."""
"""Test exceptions during keepalive send."""
ws_client_auth._client = AsyncMock()

# TypeError
Expand Down