From a969cd360619dac4a4eec557545be1dbe0d6c7bd Mon Sep 17 00:00:00 2001 From: Joostlek Date: Fri, 27 Dec 2024 16:52:34 +0100 Subject: [PATCH 1/4] Make elevenlabs recoverable --- homeassistant/components/elevenlabs/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/elevenlabs/__init__.py b/homeassistant/components/elevenlabs/__init__.py index e8a378d56c61d2..e5807fec67c74e 100644 --- a/homeassistant/components/elevenlabs/__init__.py +++ b/homeassistant/components/elevenlabs/__init__.py @@ -6,11 +6,16 @@ from elevenlabs import AsyncElevenLabs, Model from elevenlabs.core import ApiError +from httpx import ConnectError from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_KEY, Platform from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryError +from homeassistant.exceptions import ( + ConfigEntryAuthFailed, + ConfigEntryError, + ConfigEntryNotReady, +) from homeassistant.helpers.httpx_client import get_async_client from .const import CONF_MODEL @@ -48,6 +53,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ElevenLabsConfigEntry) - model_id = entry.options[CONF_MODEL] try: model = await get_model_by_id(client, model_id) + except ConnectError as err: + raise ConfigEntryNotReady("Failed to connect") from err except ApiError as err: raise ConfigEntryAuthFailed("Auth failed") from err From 2566810ce13fa3823397e9c91c2c481850c363df Mon Sep 17 00:00:00 2001 From: Simon Sorg Date: Fri, 27 Dec 2024 17:11:44 +0000 Subject: [PATCH 2/4] Add tests for entry setup --- tests/components/elevenlabs/conftest.py | 2 +- .../components/elevenlabs/test_config_flow.py | 6 +- tests/components/elevenlabs/test_setup.py | 60 +++++++++++++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 tests/components/elevenlabs/test_setup.py diff --git a/tests/components/elevenlabs/conftest.py b/tests/components/elevenlabs/conftest.py index d410f8bccddbb3..aa8c7a964d96d7 100644 --- a/tests/components/elevenlabs/conftest.py +++ b/tests/components/elevenlabs/conftest.py @@ -42,7 +42,7 @@ def mock_async_client() -> Generator[AsyncMock]: @pytest.fixture -def mock_async_client_fail() -> Generator[AsyncMock]: +def mock_async_client_api_error() -> Generator[AsyncMock]: """Override async ElevenLabs client.""" with patch( "homeassistant.components.elevenlabs.config_flow.AsyncElevenLabs", diff --git a/tests/components/elevenlabs/test_config_flow.py b/tests/components/elevenlabs/test_config_flow.py index 95e7ab5214e0b3..8f5a0bf9e5626c 100644 --- a/tests/components/elevenlabs/test_config_flow.py +++ b/tests/components/elevenlabs/test_config_flow.py @@ -56,7 +56,9 @@ async def test_user_step( async def test_invalid_api_key( - hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_async_client_fail: AsyncMock + hass: HomeAssistant, + mock_setup_entry: AsyncMock, + mock_async_client_api_error: AsyncMock, ) -> None: """Test user step with invalid api key.""" @@ -78,7 +80,7 @@ async def test_invalid_api_key( mock_setup_entry.assert_not_called() # Reset the side effect - mock_async_client_fail.side_effect = None + mock_async_client_api_error.side_effect = None result = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/elevenlabs/test_setup.py b/tests/components/elevenlabs/test_setup.py new file mode 100644 index 00000000000000..e4f4b19ad05099 --- /dev/null +++ b/tests/components/elevenlabs/test_setup.py @@ -0,0 +1,60 @@ +"""Tests for the ElevenLabs TTS entity.""" + +from __future__ import annotations + +from collections.abc import Generator +from unittest.mock import AsyncMock, MagicMock, patch + +from httpx import ConnectError +import pytest + +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant + +from .conftest import _client_mock + +from tests.common import MockConfigEntry + + +@pytest.fixture +def mock_setup_async_client() -> Generator[AsyncMock]: + """Override async ElevenLabs client.""" + with patch( + "homeassistant.components.elevenlabs.AsyncElevenLabs", + return_value=_client_mock(), + ) as mock_async_client: + yield mock_async_client + + +@pytest.fixture +def mock_setup_async_client_connect_error() -> Generator[AsyncMock]: + """Override async ElevenLabs client.""" + client_mock = _client_mock() + client_mock.models.get_all.side_effect = ConnectError("Unknown") + with patch( + "homeassistant.components.elevenlabs.AsyncElevenLabs", return_value=client_mock + ) as mock_async_client: + yield mock_async_client + + +async def test_setup( + hass: HomeAssistant, + mock_setup_async_client: MagicMock, + mock_entry: MockConfigEntry, +) -> None: + """Test entry setup without any exceptions.""" + mock_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_entry.entry_id) + assert mock_entry.state == ConfigEntryState.LOADED + + +async def test_setup_connect_error( + hass: HomeAssistant, + mock_setup_async_client_connect_error: MagicMock, + mock_entry: MockConfigEntry, +) -> None: + """Test entry setup with a connection error.""" + mock_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_entry.entry_id) + # Ensure is not ready + assert mock_entry.state == ConfigEntryState.SETUP_RETRY From 1d66a9937b17844428dacb23f886607c92708d64 Mon Sep 17 00:00:00 2001 From: Simon Sorg Date: Sat, 28 Dec 2024 13:56:39 +0000 Subject: [PATCH 3/4] Use the same fixtures for setup and config flow --- tests/components/elevenlabs/conftest.py | 53 +++++++++++++++---- .../components/elevenlabs/test_config_flow.py | 7 ++- tests/components/elevenlabs/test_setup.py | 33 ++---------- 3 files changed, 52 insertions(+), 41 deletions(-) diff --git a/tests/components/elevenlabs/conftest.py b/tests/components/elevenlabs/conftest.py index aa8c7a964d96d7..1c261e2947acfa 100644 --- a/tests/components/elevenlabs/conftest.py +++ b/tests/components/elevenlabs/conftest.py @@ -5,6 +5,7 @@ from elevenlabs.core import ApiError from elevenlabs.types import GetVoicesResponse +from httpx import ConnectError import pytest from homeassistant.components.elevenlabs.const import CONF_MODEL, CONF_VOICE @@ -34,21 +35,55 @@ def _client_mock(): @pytest.fixture def mock_async_client() -> Generator[AsyncMock]: """Override async ElevenLabs client.""" - with patch( - "homeassistant.components.elevenlabs.config_flow.AsyncElevenLabs", - return_value=_client_mock(), - ) as mock_async_client: + with ( + patch( + "homeassistant.components.elevenlabs.AsyncElevenLabs", + return_value=_client_mock(), + ) as mock_async_client, + patch( + "homeassistant.components.elevenlabs.config_flow.AsyncElevenLabs", + new=mock_async_client, + ), + ): yield mock_async_client @pytest.fixture def mock_async_client_api_error() -> Generator[AsyncMock]: + """Override async ElevenLabs client with ApiError side effect.""" + client_mock = _client_mock() + client_mock.models.get_all.side_effect = ApiError + client_mock.voices.get_all.side_effect = ApiError + + with ( + patch( + "homeassistant.components.elevenlabs.AsyncElevenLabs", + return_value=client_mock, + ) as mock_async_client, + patch( + "homeassistant.components.elevenlabs.config_flow.AsyncElevenLabs", + new=mock_async_client, + ), + ): + yield mock_async_client + + +@pytest.fixture +def mock_async_client_connect_error() -> Generator[AsyncMock]: """Override async ElevenLabs client.""" - with patch( - "homeassistant.components.elevenlabs.config_flow.AsyncElevenLabs", - return_value=_client_mock(), - ) as mock_async_client: - mock_async_client.side_effect = ApiError + client_mock = _client_mock() + client_mock.models.get_all.side_effect = ConnectError("Unknown") + client_mock.voices.get_all.side_effect = ConnectError("Unknown") + with ( + patch( + "homeassistant.components.elevenlabs.AsyncElevenLabs", + return_value=client_mock, + ) as mock_async_client, + patch( + "homeassistant.components.elevenlabs.config_flow.AsyncElevenLabs", + new=mock_async_client, + ), + ): yield mock_async_client diff --git a/tests/components/elevenlabs/test_config_flow.py b/tests/components/elevenlabs/test_config_flow.py index 8f5a0bf9e5626c..7eeb0a6eb469d2 100644 --- a/tests/components/elevenlabs/test_config_flow.py +++ b/tests/components/elevenlabs/test_config_flow.py @@ -2,6 +2,8 @@ from unittest.mock import AsyncMock +import pytest + from homeassistant.components.elevenlabs.const import ( CONF_CONFIGURE_VOICE, CONF_MODEL, @@ -59,6 +61,7 @@ async def test_invalid_api_key( hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_async_client_api_error: AsyncMock, + request: pytest.FixtureRequest, ) -> None: """Test user step with invalid api key.""" @@ -79,8 +82,8 @@ async def test_invalid_api_key( mock_setup_entry.assert_not_called() - # Reset the side effect - mock_async_client_api_error.side_effect = None + # Use a working client + request.getfixturevalue("mock_async_client") result = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/elevenlabs/test_setup.py b/tests/components/elevenlabs/test_setup.py index e4f4b19ad05099..7a770b04142033 100644 --- a/tests/components/elevenlabs/test_setup.py +++ b/tests/components/elevenlabs/test_setup.py @@ -2,44 +2,17 @@ from __future__ import annotations -from collections.abc import Generator -from unittest.mock import AsyncMock, MagicMock, patch - -from httpx import ConnectError -import pytest +from unittest.mock import MagicMock from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant -from .conftest import _client_mock - from tests.common import MockConfigEntry -@pytest.fixture -def mock_setup_async_client() -> Generator[AsyncMock]: - """Override async ElevenLabs client.""" - with patch( - "homeassistant.components.elevenlabs.AsyncElevenLabs", - return_value=_client_mock(), - ) as mock_async_client: - yield mock_async_client - - -@pytest.fixture -def mock_setup_async_client_connect_error() -> Generator[AsyncMock]: - """Override async ElevenLabs client.""" - client_mock = _client_mock() - client_mock.models.get_all.side_effect = ConnectError("Unknown") - with patch( - "homeassistant.components.elevenlabs.AsyncElevenLabs", return_value=client_mock - ) as mock_async_client: - yield mock_async_client - - async def test_setup( hass: HomeAssistant, - mock_setup_async_client: MagicMock, + mock_async_client: MagicMock, mock_entry: MockConfigEntry, ) -> None: """Test entry setup without any exceptions.""" @@ -50,7 +23,7 @@ async def test_setup( async def test_setup_connect_error( hass: HomeAssistant, - mock_setup_async_client_connect_error: MagicMock, + mock_async_client_connect_error: MagicMock, mock_entry: MockConfigEntry, ) -> None: """Test entry setup with a connection error.""" From 5a7fab091650740c0522f46718e7952d0d5c8e8d Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sun, 29 Dec 2024 14:17:19 +0100 Subject: [PATCH 4/4] Update tests/components/elevenlabs/test_setup.py Co-authored-by: Simon <80467011+sorgfresser@users.noreply.github.com> --- tests/components/elevenlabs/test_setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/components/elevenlabs/test_setup.py b/tests/components/elevenlabs/test_setup.py index 7a770b04142033..18b90ca35619a9 100644 --- a/tests/components/elevenlabs/test_setup.py +++ b/tests/components/elevenlabs/test_setup.py @@ -19,6 +19,9 @@ async def test_setup( mock_entry.add_to_hass(hass) await hass.config_entries.async_setup(mock_entry.entry_id) assert mock_entry.state == ConfigEntryState.LOADED + # Unload + await hass.config_entries.async_unload(mock_entry.entry_id) + assert mock_entry.state == ConfigEntryState.NOT_LOADED async def test_setup_connect_error(