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
71 changes: 70 additions & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import json
import logging
from unittest import mock
from unittest.mock import AsyncMock, MagicMock, patch

import aiohttp
import pytest
Expand All @@ -12,18 +13,19 @@
from awesomeversion.exceptions import AwesomeVersionCompareException

import openevsehttp.__main__ as main
from openevsehttp.__main__ import OpenEVSE
from openevsehttp.exceptions import (
InvalidType,
MissingSerial,
UnknownError,
UnsupportedFeature,
)
from tests.common import load_fixture
from openevsehttp.websocket import (
SIGNAL_CONNECTION_STATE,
STATE_CONNECTED,
STATE_DISCONNECTED,
)
from tests.common import load_fixture

pytestmark = pytest.mark.asyncio

Expand All @@ -43,6 +45,7 @@
TEST_URL_GITHUB_v2 = (
"https://api.github.com/repos/OpenEVSE/ESP8266_WiFi_v2.x/releases/latest"
)
SERVER_URL = "openevse.test.tld"


async def test_get_status_auth(test_charger_auth):
Expand Down Expand Up @@ -2209,3 +2212,69 @@ async def test_set_divert_mode(
with caplog.at_level(logging.DEBUG):
await test_charger_new.set_divert_mode("fast")
assert "Problem issuing command: error" in caplog.text


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

# Setup mock session to be an async context manager
mock_session = MagicMock()
mock_session.__aenter__ = AsyncMock(return_value=mock_session)
mock_session.__aexit__ = AsyncMock(return_value=None)

# Setup mock response context to be an async context manager
mock_resp = AsyncMock()
mock_resp.status = 200
mock_resp.text.return_value = "{}"

mock_request_ctx = MagicMock()
mock_request_ctx.__aenter__ = AsyncMock(return_value=mock_resp)
mock_request_ctx.__aexit__ = AsyncMock(return_value=None)

# Ensure session.get() returns the request context
mock_session.get.return_value = mock_request_ctx

with patch("aiohttp.ClientSession", return_value=mock_session), patch(
"aiohttp.BasicAuth"
) as mock_basic_auth:

await charger.update()

# Verify BasicAuth was instantiated
# Note: process_request is called multiple times in update(), so we check if called at least once
mock_basic_auth.assert_called_with("user", "password")


async def test_main_sync_callback():
"""Test synchronous callback in _update_status (covers __main__.py:293)."""
charger = OpenEVSE(SERVER_URL)
sync_callback = MagicMock()
charger.callback = sync_callback

# Manually trigger update status
await charger._update_status("data", {"key": "value"}, None)

sync_callback.assert_called_once()


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

# Mock response with 'msg' but no 'ret'
with patch.object(charger, "process_request", return_value={"msg": "ErrorMsg"}):
cmd, ret = await charger.send_command("$ST")
assert cmd is False
assert ret == "ErrorMsg"


async def test_send_command_empty_fallback():
"""Test send_command empty fallback."""
charger = OpenEVSE(SERVER_URL)

# Mock response with neither 'msg' nor 'ret'
with patch.object(charger, "process_request", return_value={}):
cmd, ret = await charger.send_command("$ST")
assert cmd is False
assert ret == ""
61 changes: 61 additions & 0 deletions tests/test_main_edge_cases.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""Edge case tests for OpenEVSE main library."""

import logging
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from openevsehttp.__main__ import OpenEVSE

pytestmark = pytest.mark.asyncio

SERVER_URL = "openevse.test.tld"


@pytest.fixture
def charger():
return OpenEVSE(SERVER_URL)


async def test_process_request_decode_error(charger, caplog):
"""Test handling of UnicodeDecodeError in process_request."""
mock_resp = MagicMock()

# .text() needs to be an async mock that raises the error
mock_resp.text = AsyncMock(
side_effect=UnicodeDecodeError("utf-8", b"", 0, 1, "err")
)

# .read() needs to be an async mock that returns the bytes
mock_resp.read = AsyncMock(return_value=b'{"msg": "decoded"}')

mock_resp.status = 200
mock_resp.__aenter__.return_value = mock_resp
mock_resp.__aexit__.return_value = None

with patch("aiohttp.ClientSession.get", return_value=mock_resp), caplog.at_level(
logging.DEBUG
):
data = await charger.process_request("http://url", method="get")

assert data == {"msg": "decoded"}
assert "Decoding error" in caplog.text


async def test_process_request_http_warnings(charger, caplog):
"""Test logging of specific HTTP error codes."""
mock_resp = MagicMock()

# .text() needs to be an async mock that returns the string
mock_resp.text = AsyncMock(return_value='{"msg": "Not Found"}')

mock_resp.status = 404
mock_resp.__aenter__.return_value = mock_resp
mock_resp.__aexit__.return_value = None

with patch("aiohttp.ClientSession.get", return_value=mock_resp), caplog.at_level(
logging.WARNING
):
await charger.process_request("http://url", method="get")
# Verify the 404 response body was logged as a warning
assert "{'msg': 'Not Found'}" in caplog.text
Loading