Make elevenlabs recoverable (#134094)

* Make elevenlabs recoverable

* Add tests for entry setup

* Use the same fixtures for setup and config flow

* Update tests/components/elevenlabs/test_setup.py

Co-authored-by: Simon <80467011+sorgfresser@users.noreply.github.com>

---------

Co-authored-by: Simon Sorg <simon.sorg@student.hpi.de>
Co-authored-by: G Johansson <goran.johansson@shiftit.se>
Co-authored-by: Simon <80467011+sorgfresser@users.noreply.github.com>
This commit is contained in:
Joost Lekkerkerker 2024-12-29 14:26:59 +01:00 committed by GitHub
parent 873b078bb3
commit c23f5c9f2c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 97 additions and 14 deletions

View File

@ -6,11 +6,16 @@ from dataclasses import dataclass
from elevenlabs import AsyncElevenLabs, Model from elevenlabs import AsyncElevenLabs, Model
from elevenlabs.core import ApiError from elevenlabs.core import ApiError
from httpx import ConnectError
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, Platform from homeassistant.const import CONF_API_KEY, Platform
from homeassistant.core import HomeAssistant 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 homeassistant.helpers.httpx_client import get_async_client
from .const import CONF_MODEL from .const import CONF_MODEL
@ -48,6 +53,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ElevenLabsConfigEntry) -
model_id = entry.options[CONF_MODEL] model_id = entry.options[CONF_MODEL]
try: try:
model = await get_model_by_id(client, model_id) model = await get_model_by_id(client, model_id)
except ConnectError as err:
raise ConfigEntryNotReady("Failed to connect") from err
except ApiError as err: except ApiError as err:
raise ConfigEntryAuthFailed("Auth failed") from err raise ConfigEntryAuthFailed("Auth failed") from err

View File

@ -5,6 +5,7 @@ from unittest.mock import AsyncMock, patch
from elevenlabs.core import ApiError from elevenlabs.core import ApiError
from elevenlabs.types import GetVoicesResponse from elevenlabs.types import GetVoicesResponse
from httpx import ConnectError
import pytest import pytest
from homeassistant.components.elevenlabs.const import CONF_MODEL, CONF_VOICE from homeassistant.components.elevenlabs.const import CONF_MODEL, CONF_VOICE
@ -34,21 +35,55 @@ def _client_mock():
@pytest.fixture @pytest.fixture
def mock_async_client() -> Generator[AsyncMock]: def mock_async_client() -> Generator[AsyncMock]:
"""Override async ElevenLabs client.""" """Override async ElevenLabs client."""
with patch( with (
"homeassistant.components.elevenlabs.config_flow.AsyncElevenLabs", patch(
return_value=_client_mock(), "homeassistant.components.elevenlabs.AsyncElevenLabs",
) as mock_async_client: return_value=_client_mock(),
) as mock_async_client,
patch(
"homeassistant.components.elevenlabs.config_flow.AsyncElevenLabs",
new=mock_async_client,
),
):
yield mock_async_client yield mock_async_client
@pytest.fixture @pytest.fixture
def mock_async_client_fail() -> Generator[AsyncMock]: 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.""" """Override async ElevenLabs client."""
with patch( client_mock = _client_mock()
"homeassistant.components.elevenlabs.config_flow.AsyncElevenLabs", client_mock.models.get_all.side_effect = ConnectError("Unknown")
return_value=_client_mock(), client_mock.voices.get_all.side_effect = ConnectError("Unknown")
) as mock_async_client: with (
mock_async_client.side_effect = ApiError 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 yield mock_async_client

View File

@ -2,6 +2,8 @@
from unittest.mock import AsyncMock from unittest.mock import AsyncMock
import pytest
from homeassistant.components.elevenlabs.const import ( from homeassistant.components.elevenlabs.const import (
CONF_CONFIGURE_VOICE, CONF_CONFIGURE_VOICE,
CONF_MODEL, CONF_MODEL,
@ -56,7 +58,10 @@ async def test_user_step(
async def test_invalid_api_key( 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,
request: pytest.FixtureRequest,
) -> None: ) -> None:
"""Test user step with invalid api key.""" """Test user step with invalid api key."""
@ -77,8 +82,8 @@ async def test_invalid_api_key(
mock_setup_entry.assert_not_called() mock_setup_entry.assert_not_called()
# Reset the side effect # Use a working client
mock_async_client_fail.side_effect = None request.getfixturevalue("mock_async_client")
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],

View File

@ -0,0 +1,36 @@
"""Tests for the ElevenLabs TTS entity."""
from __future__ import annotations
from unittest.mock import MagicMock
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
async def test_setup(
hass: HomeAssistant,
mock_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
# Unload
await hass.config_entries.async_unload(mock_entry.entry_id)
assert mock_entry.state == ConfigEntryState.NOT_LOADED
async def test_setup_connect_error(
hass: HomeAssistant,
mock_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