From 7635c81fa3ad293607dd725fb7c29d6fee5af01a Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Thu, 1 Oct 2026 20:50:49 +0200 Subject: [PATCH] Don't trigger reauth on connection errors for Mikrotik (#183904) --- .../components/mikrotik/coordinator.py | 10 +++---- tests/components/mikrotik/test_init.py | 29 ++++++++++++++++++- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/mikrotik/coordinator.py b/homeassistant/components/mikrotik/coordinator.py index 0f7a97cc1633..5bcc36f847b4 100644 --- a/homeassistant/components/mikrotik/coordinator.py +++ b/homeassistant/components/mikrotik/coordinator.py @@ -445,11 +445,11 @@ def get_api(entry: dict[str, Any]) -> librouteros.Api: ) _error = None break - except ( - librouteros.exceptions.LibRouterosError, - OSError, - TimeoutError, - ) as api_error: + except CONNECTION_ERRORS as api_error: + LOGGER.debug("Mikrotik %s error: %s", entry[CONF_HOST], api_error) + # Falling back to token login would mask this with a bogus auth error + raise CannotConnect from api_error + except librouteros.exceptions.LibRouterosError as api_error: _error = api_error if _error is not None: diff --git a/tests/components/mikrotik/test_init.py b/tests/components/mikrotik/test_init.py index 1e833aebc9a4..566acde7d13a 100644 --- a/tests/components/mikrotik/test_init.py +++ b/tests/components/mikrotik/test_init.py @@ -6,7 +6,7 @@ from typing import Any from unittest.mock import MagicMock, patch from freezegun.api import FrozenDateTimeFactory -from librouteros.exceptions import ConnectionClosed, LibRouterosError +from librouteros.exceptions import ConnectionClosed, LibRouterosError, TrapError import pytest from homeassistant.components import mikrotik @@ -156,6 +156,33 @@ async def test_hub_login_error_starts_reauth( assert any(entry.async_get_active_flows(hass, {SOURCE_REAUTH})) +@pytest.mark.parametrize( + "error", + [ConnectionClosed(), OSError(), TimeoutError()], + ids=["connection_closed", "os_error", "timeout_error"], +) +async def test_hub_connect_error_skips_token_login_fallback( + hass: HomeAssistant, + mock_config_entry: MockConfigEntryFactory, + error: Exception, +) -> None: + """Test a connection error on plain login doesn't fall back to token login.""" + entry = mock_config_entry() + entry.add_to_hass(hass) + + # Modern RouterOS rejects the legacy token login as invalid credentials + with patch( + "librouteros.connect", + side_effect=[error, TrapError("invalid user name or password (6)")], + ) as mock_connect: + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert mock_connect.call_count == 1 + assert entry.state is ConfigEntryState.SETUP_RETRY + assert not any(entry.async_get_active_flows(hass, {SOURCE_REAUTH})) + + async def test_optional_command_error_is_suppressed( hass: HomeAssistant, mock_api: MagicMock, mock_config_entry: MockConfigEntryFactory ) -> None: