From 47978eb3ed8cd53701c91b8d93584b826d526d2b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 17 Jan 2026 19:37:37 +0000 Subject: [PATCH 1/3] Initial plan From cdc6fe0ddfe264041aa7dadfa17ba6c842a8ee0b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 17 Jan 2026 19:43:41 +0000 Subject: [PATCH 2/3] Add test coverage for get_mqtt_client_for_installation method Co-authored-by: AndyTempel <9637751+AndyTempel@users.noreply.github.com> --- tests/test_client.py | 121 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/tests/test_client.py b/tests/test_client.py index ce23cae..65fdcd5 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -7,8 +7,9 @@ import random from victron_vrm import VictronVRMClient -from victron_vrm.exceptions import VictronVRMError, AuthorizationError +from victron_vrm.exceptions import VictronVRMError, AuthorizationError, NotFoundError, ClientError from victron_vrm.models import Site +from victron_vrm.mqtt import VRMMQTTClient # Set up logging logging.basicConfig( @@ -373,3 +374,121 @@ async def test_stats_false_to_none_transformation(vrm_client): except VictronVRMError as e: logger.warning(f"Error in mock test: {e}") pytest.skip(f"Error in mock test: {e}") + + +@pytest.mark.asyncio +async def test_get_mqtt_client_for_installation_success(): + """Test getting MQTT client for a valid installation.""" + import unittest.mock + from victron_vrm.models.auth import AuthToken + + # Create a mock client without needing actual network access + async with VictronVRMClient(token="mock_token", token_type="Bearer") as client: + # Mock the internal methods directly at asyncio.gather level + mock_token = AuthToken( + access_token="mock_access_token", + token_type="Bearer", + expires_in=3600, + scope="read" + ) + + # Create a minimal mock object with just the attributes we need + class MockUser: + email = "test@example.com" + + class MockSite: + identifier = "test-vrm-id" + mqtt_hostname = "mqtt.victronenergy.com" + + async def mock_gather(*args): + return mock_token, MockUser(), [MockSite()] + + # Mock asyncio.gather to avoid actual API calls + with unittest.mock.patch("asyncio.gather", side_effect=mock_gather): + # Get MQTT client + mqtt_client = await client.get_mqtt_client_for_installation(67890) + + # Verify the client is properly configured + assert mqtt_client is not None + assert isinstance(mqtt_client, VRMMQTTClient) + + # Verify client attributes + assert mqtt_client.host == "mqtt.victronenergy.com" + assert mqtt_client.username == "test@example.com" + assert mqtt_client.password == "Bearer mock_access_token" + assert mqtt_client.installation_id == "test-vrm-id" + + logger.info("Successfully created MQTT client with mocked data") + + +@pytest.mark.asyncio +async def test_get_mqtt_client_for_installation_not_found(): + """Test getting MQTT client for a non-existent installation.""" + import unittest.mock + from victron_vrm.models.auth import AuthToken + + # Create a mock client + async with VictronVRMClient(token="mock_token", token_type="Bearer") as client: + mock_token = AuthToken( + access_token="mock_access_token", + token_type="Bearer", + expires_in=3600, + scope="read" + ) + + class MockUser: + email = "test@example.com" + + async def mock_gather(*args): + # Return empty list for installations + return mock_token, MockUser(), [] + + # Mock asyncio.gather to return empty installations list + with unittest.mock.patch("asyncio.gather", side_effect=mock_gather): + # Should raise NotFoundError + with pytest.raises(NotFoundError) as exc_info: + await client.get_mqtt_client_for_installation(999999999) + + # Verify the error message + assert "not found" in str(exc_info.value).lower() + assert "999999999" in str(exc_info.value) + + logger.info("NotFoundError correctly raised for invalid installation ID") + + +@pytest.mark.asyncio +async def test_get_mqtt_client_for_installation_missing_hostname(): + """Test getting MQTT client for installation without MQTT hostname.""" + import unittest.mock + from victron_vrm.models.auth import AuthToken + + # Create a mock client + async with VictronVRMClient(token="mock_token", token_type="Bearer") as client: + mock_token = AuthToken( + access_token="mock_access_token", + token_type="Bearer", + expires_in=3600, + scope="read" + ) + + class MockUser: + email = "test@example.com" + + class MockSite: + identifier = "test-vrm-id" + mqtt_hostname = None # No MQTT hostname + + async def mock_gather(*args): + return mock_token, MockUser(), [MockSite()] + + # Mock asyncio.gather + with unittest.mock.patch("asyncio.gather", side_effect=mock_gather): + # Should raise ClientError + with pytest.raises(ClientError) as exc_info: + await client.get_mqtt_client_for_installation(67890) + + # Verify the error message + assert "mqtt hostname" in str(exc_info.value).lower() + assert "67890" in str(exc_info.value) + + logger.info("ClientError correctly raised for installation without MQTT hostname") From abeb37c706d9c3212b39e4f8bc9aabb94adadf0e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 17 Jan 2026 19:45:29 +0000 Subject: [PATCH 3/3] Refactor test code: move imports to top and reduce duplication Co-authored-by: AndyTempel <9637751+AndyTempel@users.noreply.github.com> --- tests/test_client.py | 85 +++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 52 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 65fdcd5..6722225 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,6 +1,7 @@ """Tests for the Victron Energy VRM API client.""" import logging +import unittest.mock import aiohttp import pytest @@ -9,6 +10,7 @@ from victron_vrm import VictronVRMClient from victron_vrm.exceptions import VictronVRMError, AuthorizationError, NotFoundError, ClientError from victron_vrm.models import Site +from victron_vrm.models.auth import AuthToken from victron_vrm.mqtt import VRMMQTTClient # Set up logging @@ -22,6 +24,34 @@ AUTH_DEMO_URL = "https://vrmapi.victronenergy.com/v2/auth/loginAsDemo" +# Helper classes for mocking +class _MockUser: + """Mock user for testing.""" + email = "test@example.com" + + +class _MockSite: + """Mock site for testing.""" + identifier = "test-vrm-id" + mqtt_hostname = "mqtt.victronenergy.com" + + +class _MockSiteNoHostname: + """Mock site without MQTT hostname for testing.""" + identifier = "test-vrm-id" + mqtt_hostname = None + + +def _create_mock_token(): + """Create a mock auth token for testing.""" + return AuthToken( + access_token="mock_access_token", + token_type="Bearer", + expires_in=3600, + scope="read" + ) + + @pytest.fixture(scope="session") async def demo_token(): """Get a demo token for testing (session-scoped to avoid 429s).""" @@ -379,29 +409,10 @@ async def test_stats_false_to_none_transformation(vrm_client): @pytest.mark.asyncio async def test_get_mqtt_client_for_installation_success(): """Test getting MQTT client for a valid installation.""" - import unittest.mock - from victron_vrm.models.auth import AuthToken - # Create a mock client without needing actual network access async with VictronVRMClient(token="mock_token", token_type="Bearer") as client: - # Mock the internal methods directly at asyncio.gather level - mock_token = AuthToken( - access_token="mock_access_token", - token_type="Bearer", - expires_in=3600, - scope="read" - ) - - # Create a minimal mock object with just the attributes we need - class MockUser: - email = "test@example.com" - - class MockSite: - identifier = "test-vrm-id" - mqtt_hostname = "mqtt.victronenergy.com" - async def mock_gather(*args): - return mock_token, MockUser(), [MockSite()] + return _create_mock_token(), _MockUser(), [_MockSite()] # Mock asyncio.gather to avoid actual API calls with unittest.mock.patch("asyncio.gather", side_effect=mock_gather): @@ -424,24 +435,11 @@ async def mock_gather(*args): @pytest.mark.asyncio async def test_get_mqtt_client_for_installation_not_found(): """Test getting MQTT client for a non-existent installation.""" - import unittest.mock - from victron_vrm.models.auth import AuthToken - # Create a mock client async with VictronVRMClient(token="mock_token", token_type="Bearer") as client: - mock_token = AuthToken( - access_token="mock_access_token", - token_type="Bearer", - expires_in=3600, - scope="read" - ) - - class MockUser: - email = "test@example.com" - async def mock_gather(*args): # Return empty list for installations - return mock_token, MockUser(), [] + return _create_mock_token(), _MockUser(), [] # Mock asyncio.gather to return empty installations list with unittest.mock.patch("asyncio.gather", side_effect=mock_gather): @@ -459,27 +457,10 @@ async def mock_gather(*args): @pytest.mark.asyncio async def test_get_mqtt_client_for_installation_missing_hostname(): """Test getting MQTT client for installation without MQTT hostname.""" - import unittest.mock - from victron_vrm.models.auth import AuthToken - # Create a mock client async with VictronVRMClient(token="mock_token", token_type="Bearer") as client: - mock_token = AuthToken( - access_token="mock_access_token", - token_type="Bearer", - expires_in=3600, - scope="read" - ) - - class MockUser: - email = "test@example.com" - - class MockSite: - identifier = "test-vrm-id" - mqtt_hostname = None # No MQTT hostname - async def mock_gather(*args): - return mock_token, MockUser(), [MockSite()] + return _create_mock_token(), _MockUser(), [_MockSiteNoHostname()] # Mock asyncio.gather with unittest.mock.patch("asyncio.gather", side_effect=mock_gather):