From d116c86e3e8156914daf54e912513bb5a2054c0a Mon Sep 17 00:00:00 2001 From: Marc-Olivier Arsenault Date: Mon, 26 Aug 2024 22:01:29 -0400 Subject: [PATCH] Errors when off (#394) * throw PlatformNotReady when printer is not reachable * tweak testing * add tests --- custom_components/moonraker/__init__.py | 40 ++++++++++++++++-------- tests/conftest.py | 9 ++++++ tests/test_binary_sensor.py | 6 ++++ tests/test_button.py | 6 ++++ tests/test_camera.py | 6 ++++ tests/test_config_flow.py | 6 ++++ tests/test_init.py | 41 ++++++++++++++++++++----- tests/test_number.py | 6 ++++ tests/test_sensor.py | 6 ++++ tests/test_switch.py | 6 ++++ 10 files changed, 112 insertions(+), 20 deletions(-) diff --git a/custom_components/moonraker/__init__.py b/custom_components/moonraker/__init__.py index b58118e..ef7fd64 100755 --- a/custom_components/moonraker/__init__.py +++ b/custom_components/moonraker/__init__.py @@ -5,11 +5,12 @@ import os.path import uuid from datetime import timedelta +import socket import async_timeout from homeassistant.config_entries import ConfigEntry from homeassistant.core import Config, HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers import device_registry as dr from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed @@ -54,6 +55,17 @@ def get_user_name(hass: HomeAssistant, entry: ConfigEntry): return device_entries[0].name_by_user +def is_open(ip, port): + """Check if port is open.""" + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((ip, int(port))) + s.shutdown(2) + return True + except Exception: + return False + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up this integration using UI.""" @@ -87,20 +99,24 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): try: async with async_timeout.timeout(TIMEOUT): - await api.start() - printer_info = await api.client.call_method("printer.info") - _LOGGER.debug(printer_info) + if is_open(url, port): + await api.start() + printer_info = await api.client.call_method("printer.info") + _LOGGER.debug(printer_info) - if printer_name == "": - api_device_name = printer_info[HOSTNAME] - else: - api_device_name = printer_name + if printer_name == "": + api_device_name = printer_info[HOSTNAME] + else: + api_device_name = printer_name - hass.config_entries.async_update_entry(entry, title=api_device_name) + hass.config_entries.async_update_entry(entry, title=api_device_name) + else: + _LOGGER.warning("Cannot connect to moonraker instance") + raise PlatformNotReady(f"Error connecting to {url}:{port}") - except Exception as exc: + except Exception: _LOGGER.warning("Cannot configure moonraker instance") - raise ConfigEntryNotReady(f"Error connecting to {url}:{port}") from exc + raise PlatformNotReady(f"Error connecting to {url}:{port}") coordinator = MoonrakerDataUpdateCoordinator( hass, client=api, config_entry=entry, api_device_name=api_device_name @@ -109,7 +125,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): await coordinator.async_refresh() if not coordinator.last_update_success: - raise ConfigEntryNotReady + raise PlatformNotReady hass.data[DOMAIN][entry.entry_id] = coordinator for platform in PLATFORMS: diff --git a/tests/conftest.py b/tests/conftest.py index 598d19c..c83f6da 100755 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -442,3 +442,12 @@ def get_moonraker_default_mock(get_default_api_response): return_value=get_default_api_response, ): yield + + +@pytest.fixture(name="skip_connection_check") +def skip_connection_check_fixture(): + """Skip skip_connection_check .""" + with ( + patch("custom_components.moonraker.is_open"), + ): + yield diff --git a/tests/test_binary_sensor.py b/tests/test_binary_sensor.py index b0e505a..9465b30 100644 --- a/tests/test_binary_sensor.py +++ b/tests/test_binary_sensor.py @@ -18,6 +18,12 @@ def bypass_connect_client_fixture(): yield +@pytest.fixture(name="bypass_connection_test", autouse=True) +def bypass_connection_test_fixture(skip_connection_check): + """Skip calls to get data from API.""" + yield + + async def test_runout_filament_sensor_missing(hass, get_data, get_printer_objects_list): """Test.""" get_data["status"].pop("filament_switch_sensor filament_sensor_1", None) diff --git a/tests/test_button.py b/tests/test_button.py index 3a7eb26..d31e73d 100644 --- a/tests/test_button.py +++ b/tests/test_button.py @@ -21,6 +21,12 @@ def bypass_connect_client_fixture(): yield +@pytest.fixture(name="bypass_connection_test", autouse=True) +def bypass_connection_test_fixture(skip_connection_check): + """Skip calls to get data from API.""" + yield + + @pytest.mark.parametrize( "button, method", [ diff --git a/tests/test_camera.py b/tests/test_camera.py index cdcf9a6..a764a38 100755 --- a/tests/test_camera.py +++ b/tests/test_camera.py @@ -30,6 +30,12 @@ def bypass_connect_client_fixture(): yield +@pytest.fixture(name="bypass_connection_test", autouse=True) +def bypass_connection_test_fixture(skip_connection_check): + """Skip calls to get data from API.""" + yield + + async def test_camera_services(hass, caplog): """Test camera services.""" diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index bbab8f4..a44937a 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -20,6 +20,12 @@ from .const import MOCK_CONFIG, MOCK_OPTIONS +@pytest.fixture(name="bypass_connection_test", autouse=True) +def bypass_connection_test_fixture(skip_connection_check): + """Skip calls to get data from API.""" + yield + + @pytest.fixture(name="bypass_connect_client") def bypass_connect_client_fixture(): """Skip calls to get data from API.""" diff --git a/tests/test_init.py b/tests/test_init.py index 0c29816..ddbc9ce 100755 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -3,7 +3,7 @@ from unittest.mock import patch import pytest -from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.update_coordinator import UpdateFailed from pytest_homeassistant_custom_component.common import MockConfigEntry @@ -25,7 +25,13 @@ def bypass_connect_client_fixture(): yield -async def test_setup_unload_and_reload_entry(hass): +@pytest.fixture(name="bypass_connection_test") +def bypass_connection_test_fixture(skip_connection_check): + """Skip calls to get data from API.""" + yield + + +async def test_setup_unload_and_reload_entry(hass, bypass_connection_test): """Test entry setup and unload.""" # Create a mock entry so we don't have to go through config flow @@ -51,7 +57,7 @@ async def test_setup_unload_and_reload_entry(hass): assert config_entry.entry_id not in hass.data[DOMAIN] -async def test_setup_unload_and_reload_entry_with_name(hass): +async def test_setup_unload_and_reload_entry_with_name(hass, bypass_connection_test): """Test entry setup with name and unload.""" # Create a mock entry so we don't have to go through config flow @@ -79,7 +85,7 @@ async def test_setup_unload_and_reload_entry_with_name(hass): assert config_entry.entry_id not in hass.data[DOMAIN] -async def test_async_send_data_exception(hass): +async def test_async_send_data_exception(hass, bypass_connection_test): """Test async_post_exception.""" config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="test") @@ -100,7 +106,7 @@ async def test_async_send_data_exception(hass): assert await async_unload_entry(hass, config_entry) -async def test_setup_entry_exception(hass): +async def test_setup_entry_exception(hass, bypass_connection_test): """Test ConfigEntryNotReady when API raises an exception during entry setup.""" with patch( "moonraker_api.MoonrakerClient.call_method", @@ -109,7 +115,7 @@ async def test_setup_entry_exception(hass): config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="test") config_entry.add_to_hass(hass) - with pytest.raises(ConfigEntryNotReady): + with pytest.raises(PlatformNotReady): assert await async_setup_entry(hass, config_entry) @@ -121,7 +127,7 @@ def load_data(endpoint, *args, **kwargs): raise Exception -async def test_failed_first_refresh(hass): +async def test_failed_first_refresh(hass, bypass_connection_test): """Test ConfigEntryNotReady when API raises an exception during entry setup.""" with patch( "moonraker_api.MoonrakerClient.call_method", @@ -130,5 +136,24 @@ async def test_failed_first_refresh(hass): config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="test") config_entry.add_to_hass(hass) - with pytest.raises(ConfigEntryNotReady): + with pytest.raises(PlatformNotReady): + assert await async_setup_entry(hass, config_entry) + + +async def test_is_on(hass): + """Test connection is working.""" + with patch("socket.socket"): + config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="test") + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + +async def test_is_off(hass): + """Test connection is working.""" + with patch("socket.socket", side_effect=Exception("mocked error")): + config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="test") + config_entry.add_to_hass(hass) + + with pytest.raises(PlatformNotReady): assert await async_setup_entry(hass, config_entry) diff --git a/tests/test_number.py b/tests/test_number.py index a7c1e29..39ad27c 100644 --- a/tests/test_number.py +++ b/tests/test_number.py @@ -20,6 +20,12 @@ def bypass_connect_client_fixture(): yield +@pytest.fixture(name="bypass_connection_test", autouse=True) +def bypass_connection_test_fixture(skip_connection_check): + """Skip calls to get data from API.""" + yield + + # test number @pytest.mark.parametrize( "number", diff --git a/tests/test_sensor.py b/tests/test_sensor.py index 57393e6..4fc7038 100755 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -27,6 +27,12 @@ def bypass_connect_client_fixture(): yield +@pytest.fixture(name="bypass_connection_test", autouse=True) +def bypass_connection_test_fixture(skip_connection_check): + """Skip calls to get data from API.""" + yield + + @pytest.fixture(name="data_for_calculate_pct") def data_for_calculate_pct_fixture(): """data_for_calculate_pct.""" diff --git a/tests/test_switch.py b/tests/test_switch.py index b10a8ed..1bf45a7 100644 --- a/tests/test_switch.py +++ b/tests/test_switch.py @@ -20,6 +20,12 @@ def bypass_connect_client_fixture(): yield +@pytest.fixture(name="bypass_connection_test", autouse=True) +def bypass_connection_test_fixture(skip_connection_check): + """Skip calls to get data from API.""" + yield + + # test switches @pytest.mark.parametrize( "switch, switch_type",