diff --git a/openevsehttp/__main__.py b/openevsehttp/__main__.py index 7996435..7548bd1 100644 --- a/openevsehttp/__main__.py +++ b/openevsehttp/__main__.py @@ -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) diff --git a/tests/fixtures/v4_json/status-new.json b/tests/fixtures/v4_json/status-new.json index 67d42dc..cf72508 100644 --- a/tests/fixtures/v4_json/status-new.json +++ b/tests/fixtures/v4_json/status-new.json @@ -19,7 +19,7 @@ "evse_connected": 1, "amp": 0, "voltage": 220, - "power": 0, + "power": 4500, "pilot": 48, "max_current": 48, "temp": 440, diff --git a/tests/test_main.py b/tests/test_main.py index 7163648..ff848bd 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -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 @@ -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 @@ -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' @@ -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() diff --git a/tests/test_websocket.py b/tests/test_websocket.py index 36a09cf..089a60b 100644 --- a/tests/test_websocket.py +++ b/tests/test_websocket.py @@ -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) @@ -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 @@ -223,7 +223,7 @@ 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 @@ -231,7 +231,7 @@ async def test_websocket_exceptions_generic(ws_client_auth): @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 @@ -249,7 +249,7 @@ 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() @@ -257,7 +257,7 @@ async def test_keepalive_client_missing(ws_client_auth): @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