Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Make elevenlabs recoverable #134094

Merged
merged 5 commits into from
Dec 29, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion homeassistant/components/elevenlabs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
55 changes: 45 additions & 10 deletions tests/components/elevenlabs/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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_fail() -> Generator[AsyncMock]:
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


Expand Down
11 changes: 8 additions & 3 deletions tests/components/elevenlabs/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from unittest.mock import AsyncMock

import pytest

from homeassistant.components.elevenlabs.const import (
CONF_CONFIGURE_VOICE,
CONF_MODEL,
Expand Down Expand Up @@ -56,7 +58,10 @@ 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,
request: pytest.FixtureRequest,
) -> None:
"""Test user step with invalid api key."""

Expand All @@ -77,8 +82,8 @@ async def test_invalid_api_key(

mock_setup_entry.assert_not_called()

# Reset the side effect
mock_async_client_fail.side_effect = None
# Use a working client
request.getfixturevalue("mock_async_client")

result = await hass.config_entries.flow.async_configure(
result["flow_id"],
Expand Down
33 changes: 33 additions & 0 deletions tests/components/elevenlabs/test_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""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
gjohansson-ST marked this conversation as resolved.
Show resolved Hide resolved
gjohansson-ST marked this conversation as resolved.
Show resolved Hide resolved


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