From f9366e5cc778e2d2820e698c88d9c9f597742441 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 21 Jun 2023 14:58:58 +0200 Subject: [PATCH] Migrate google translate to config entries (#93803) Co-authored-by: Franck Nijhof --- .../components/google_translate/__init__.py | 21 +- .../google_translate/config_flow.py | 49 +++ .../components/google_translate/const.py | 7 +- .../components/google_translate/manifest.json | 2 + .../components/google_translate/strings.json | 15 + .../components/google_translate/tts.py | 105 ++++- homeassistant/generated/config_flows.py | 1 + homeassistant/generated/integrations.json | 2 +- tests/components/google_translate/conftest.py | 14 + .../google_translate/test_config_flow.py | 68 ++++ tests/components/google_translate/test_tts.py | 376 +++++++++++++----- 11 files changed, 557 insertions(+), 103 deletions(-) create mode 100644 homeassistant/components/google_translate/config_flow.py create mode 100644 homeassistant/components/google_translate/strings.json create mode 100644 tests/components/google_translate/conftest.py create mode 100644 tests/components/google_translate/test_config_flow.py diff --git a/homeassistant/components/google_translate/__init__.py b/homeassistant/components/google_translate/__init__.py index f7860c57d99..ac6b07bd4b3 100644 --- a/homeassistant/components/google_translate/__init__.py +++ b/homeassistant/components/google_translate/__init__.py @@ -1 +1,20 @@ -"""The google_translate component.""" +"""The Google Translate text-to-speech integration.""" +from __future__ import annotations + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant + +PLATFORMS: list[Platform] = [Platform.TTS] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Google Translate text-to-speech from a config entry.""" + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/google_translate/config_flow.py b/homeassistant/components/google_translate/config_flow.py new file mode 100644 index 00000000000..550b0f5f382 --- /dev/null +++ b/homeassistant/components/google_translate/config_flow.py @@ -0,0 +1,49 @@ +"""Config flow for Google Translate text-to-speech integration.""" +from __future__ import annotations + +from typing import Any + +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.components.tts import CONF_LANG +from homeassistant.data_entry_flow import FlowResult + +from .const import ( + CONF_TLD, + DEFAULT_LANG, + DEFAULT_TLD, + DOMAIN, + SUPPORT_LANGUAGES, + SUPPORT_TLD, +) + +STEP_USER_DATA_SCHEMA = vol.Schema( + { + vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORT_LANGUAGES), + vol.Optional(CONF_TLD, default=DEFAULT_TLD): vol.In(SUPPORT_TLD), + } +) + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Google Translate text-to-speech.""" + + VERSION = 1 + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the initial step.""" + if user_input is not None: + self._async_abort_entries_match( + { + CONF_LANG: user_input[CONF_LANG], + CONF_TLD: user_input[CONF_TLD], + } + ) + return self.async_create_entry( + title="Google Translate text-to-speech", data=user_input + ) + + return self.async_show_form(step_id="user", data_schema=STEP_USER_DATA_SCHEMA) diff --git a/homeassistant/components/google_translate/const.py b/homeassistant/components/google_translate/const.py index 78e96acc91d..0bb8663119b 100644 --- a/homeassistant/components/google_translate/const.py +++ b/homeassistant/components/google_translate/const.py @@ -1,6 +1,11 @@ -"""Constant for google_translate integration.""" +"""Constants for the Google Translate text-to-speech integration.""" from dataclasses import dataclass +CONF_TLD = "tld" +DEFAULT_LANG = "en" +DEFAULT_TLD = "com" +DOMAIN = "google_translate" + SUPPORT_LANGUAGES = [ "af", "ar", diff --git a/homeassistant/components/google_translate/manifest.json b/homeassistant/components/google_translate/manifest.json index 504925a4667..c9b955543db 100644 --- a/homeassistant/components/google_translate/manifest.json +++ b/homeassistant/components/google_translate/manifest.json @@ -2,6 +2,8 @@ "domain": "google_translate", "name": "Google Translate text-to-speech", "codeowners": [], + "config_flow": true, + "dependencies": ["repairs"], "documentation": "https://www.home-assistant.io/integrations/google_translate", "iot_class": "cloud_push", "loggers": ["gtts"], diff --git a/homeassistant/components/google_translate/strings.json b/homeassistant/components/google_translate/strings.json new file mode 100644 index 00000000000..a83e61f01f9 --- /dev/null +++ b/homeassistant/components/google_translate/strings.json @@ -0,0 +1,15 @@ +{ + "config": { + "step": { + "user": { + "data": { + "language": "Language", + "tld": "TLD" + } + } + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" + } + } +} diff --git a/homeassistant/components/google_translate/tts.py b/homeassistant/components/google_translate/tts.py index c02d262f6e5..45288e81996 100644 --- a/homeassistant/components/google_translate/tts.py +++ b/homeassistant/components/google_translate/tts.py @@ -1,33 +1,120 @@ """Support for the Google speech service.""" +from __future__ import annotations + from io import BytesIO import logging +from typing import Any from gtts import gTTS, gTTSError import voluptuous as vol -from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider +from homeassistant.components.tts import ( + CONF_LANG, + PLATFORM_SCHEMA, + Provider, + TextToSpeechEntity, + TtsAudioType, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from .const import MAP_LANG_TLD, SUPPORT_LANGUAGES, SUPPORT_TLD +from .const import ( + CONF_TLD, + DEFAULT_LANG, + DEFAULT_TLD, + MAP_LANG_TLD, + SUPPORT_LANGUAGES, + SUPPORT_TLD, +) _LOGGER = logging.getLogger(__name__) -DEFAULT_LANG = "en" - SUPPORT_OPTIONS = ["tld"] -DEFAULT_TLD = "com" - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORT_LANGUAGES), - vol.Optional("tld", default=DEFAULT_TLD): vol.In(SUPPORT_TLD), + vol.Optional(CONF_TLD, default=DEFAULT_TLD): vol.In(SUPPORT_TLD), } ) -async def async_get_engine(hass, config, discovery_info=None): +async def async_get_engine( + hass: HomeAssistant, + config: ConfigType, + discovery_info: DiscoveryInfoType | None = None, +) -> GoogleProvider: """Set up Google speech component.""" - return GoogleProvider(hass, config[CONF_LANG], config["tld"]) + return GoogleProvider(hass, config[CONF_LANG], config[CONF_TLD]) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Google Translate speech platform via config entry.""" + default_language = config_entry.data[CONF_LANG] + default_tld = config_entry.data[CONF_TLD] + async_add_entities([GoogleTTSEntity(config_entry, default_language, default_tld)]) + + +class GoogleTTSEntity(TextToSpeechEntity): + """The Google speech API entity.""" + + def __init__(self, config_entry: ConfigEntry, lang: str, tld: str) -> None: + """Init Google TTS service.""" + if lang in MAP_LANG_TLD: + self._lang = MAP_LANG_TLD[lang].lang + self._tld = MAP_LANG_TLD[lang].tld + else: + self._lang = lang + self._tld = tld + self._attr_name = f"Google {self._lang} {self._tld}" + self._attr_unique_id = config_entry.entry_id + + @property + def default_language(self): + """Return the default language.""" + return self._lang + + @property + def supported_languages(self): + """Return list of supported languages.""" + return SUPPORT_LANGUAGES + + @property + def supported_options(self): + """Return a list of supported options.""" + return SUPPORT_OPTIONS + + def get_tts_audio( + self, message: str, language: str, options: dict[str, Any] | None = None + ) -> TtsAudioType: + """Load TTS from google.""" + tld = self._tld + if language in MAP_LANG_TLD: + tld_language = MAP_LANG_TLD[language] + tld = tld_language.tld + language = tld_language.lang + if options is not None and "tld" in options: + tld = options["tld"] + + tts = gTTS(text=message, lang=language, tld=tld) + mp3_data = BytesIO() + + try: + tts.write_to_fp(mp3_data) + except gTTSError as exc: + _LOGGER.debug( + "Error during processing of TTS request %s", exc, exc_info=True + ) + raise HomeAssistantError(exc) from exc + + return "mp3", mp3_data.getvalue() class GoogleProvider(Provider): diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 96cb74cb316..05de95f902e 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -173,6 +173,7 @@ FLOWS = { "google_generative_ai_conversation", "google_mail", "google_sheets", + "google_translate", "google_travel_time", "govee_ble", "gpslogger", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 044bb8fec68..f1ad6c28828 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -2084,7 +2084,7 @@ }, "google_translate": { "integration_type": "hub", - "config_flow": false, + "config_flow": true, "iot_class": "cloud_push", "name": "Google Translate text-to-speech" }, diff --git a/tests/components/google_translate/conftest.py b/tests/components/google_translate/conftest.py new file mode 100644 index 00000000000..34132fc5c1d --- /dev/null +++ b/tests/components/google_translate/conftest.py @@ -0,0 +1,14 @@ +"""Common fixtures for the Google Translate text-to-speech tests.""" +from collections.abc import Generator +from unittest.mock import AsyncMock, patch + +import pytest + + +@pytest.fixture +def mock_setup_entry() -> Generator[AsyncMock, None, None]: + """Override async_setup_entry.""" + with patch( + "homeassistant.components.google_translate.async_setup_entry", return_value=True + ) as mock_setup_entry: + yield mock_setup_entry diff --git a/tests/components/google_translate/test_config_flow.py b/tests/components/google_translate/test_config_flow.py new file mode 100644 index 00000000000..70ad09961af --- /dev/null +++ b/tests/components/google_translate/test_config_flow.py @@ -0,0 +1,68 @@ +"""Test the Google Translate text-to-speech config flow.""" +from unittest.mock import AsyncMock + +import pytest + +from homeassistant import config_entries +from homeassistant.components.google_translate.const import CONF_TLD, DOMAIN +from homeassistant.components.tts import CONF_LANG +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResultType + +from tests.common import MockConfigEntry + +pytestmark = pytest.mark.usefixtures("mock_setup_entry") + + +async def test_user_step(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None: + """Test user step create entry result.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == FlowResultType.FORM + assert result["errors"] is None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_LANG: "de", + CONF_TLD: "de", + }, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "Google Translate text-to-speech" + assert result["data"] == { + CONF_LANG: "de", + CONF_TLD: "de", + } + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_already_configured( + hass: HomeAssistant, mock_setup_entry: AsyncMock +) -> None: + """Test user step already configured entry.""" + config_entry = MockConfigEntry( + domain=DOMAIN, data={CONF_LANG: "de", CONF_TLD: "de"} + ) + config_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == FlowResultType.FORM + assert result["errors"] is None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_LANG: "de", + CONF_TLD: "de", + }, + ) + + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "already_configured" + assert len(mock_setup_entry.mock_calls) == 0 diff --git a/tests/components/google_translate/test_tts.py b/tests/components/google_translate/test_tts.py index 6597507d334..d6669ee3c5f 100644 --- a/tests/components/google_translate/test_tts.py +++ b/tests/components/google_translate/test_tts.py @@ -1,21 +1,27 @@ """The tests for the Google speech platform.""" -from unittest.mock import patch +from __future__ import annotations + +from collections.abc import Generator +from typing import Any +from unittest.mock import MagicMock, patch from gtts import gTTSError import pytest from homeassistant.components import media_source, tts +from homeassistant.components.google_translate.const import CONF_TLD, DOMAIN from homeassistant.components.media_player import ( ATTR_MEDIA_CONTENT_ID, DOMAIN as DOMAIN_MP, SERVICE_PLAY_MEDIA, ) from homeassistant.config import async_process_ha_core_config -from homeassistant.core import HomeAssistant +from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM +from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component -from tests.common import async_mock_service +from tests.common import MockConfigEntry, async_mock_service @pytest.fixture(autouse=True) @@ -29,7 +35,7 @@ def mock_tts_cache_dir_autouse(mock_tts_cache_dir): return mock_tts_cache_dir -async def get_media_source_url(hass, media_content_id): +async def get_media_source_url(hass: HomeAssistant, media_content_id: str) -> str: """Get the media source url.""" if media_source.DOMAIN not in hass.config.components: assert await async_setup_component(hass, media_source.DOMAIN, {}) @@ -39,13 +45,13 @@ async def get_media_source_url(hass, media_content_id): @pytest.fixture -async def calls(hass): +async def calls(hass: HomeAssistant) -> list[ServiceCall]: """Mock media player calls.""" return async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA) @pytest.fixture(autouse=True) -async def setup_internal_url(hass): +async def setup_internal_url(hass: HomeAssistant) -> None: """Set up internal url.""" await async_process_ha_core_config( hass, {"internal_url": "http://example.local:8123"} @@ -53,26 +59,85 @@ async def setup_internal_url(hass): @pytest.fixture -def mock_gtts(): +def mock_gtts() -> Generator[MagicMock, None, None]: """Mock gtts.""" with patch("homeassistant.components.google_translate.tts.gTTS") as mock_gtts: yield mock_gtts -async def test_service_say(hass: HomeAssistant, mock_gtts, calls) -> None: - """Test service call say.""" +@pytest.fixture(name="setup") +async def setup_fixture( + hass: HomeAssistant, + config: dict[str, Any], + request: pytest.FixtureRequest, +) -> None: + """Set up the test environment.""" + if request.param == "mock_setup": + await mock_setup(hass, config) + elif request.param == "mock_config_entry_setup": + await mock_config_entry_setup(hass, config) + else: + raise RuntimeError("Invalid setup fixture") - await async_setup_component( - hass, tts.DOMAIN, {tts.DOMAIN: {"platform": "google_translate"}} + +@pytest.fixture(name="config") +def config_fixture() -> dict[str, Any]: + """Return config.""" + return {} + + +async def mock_setup(hass: HomeAssistant, config: dict[str, Any]) -> None: + """Mock setup.""" + assert await async_setup_component( + hass, tts.DOMAIN, {tts.DOMAIN: {CONF_PLATFORM: DOMAIN} | config} ) + +async def mock_config_entry_setup(hass: HomeAssistant, config: dict[str, Any]) -> None: + """Mock config entry setup.""" + default_config = {tts.CONF_LANG: "en", CONF_TLD: "com"} + config_entry = MockConfigEntry(domain=DOMAIN, data=default_config | config) + config_entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(config_entry.entry_id) + + +@pytest.mark.parametrize( + ("setup", "tts_service", "service_data"), + [ + ( + "mock_setup", + "google_translate_say", + { + ATTR_ENTITY_ID: "media_player.something", + tts.ATTR_MESSAGE: "There is a person at the front door.", + }, + ), + ( + "mock_config_entry_setup", + "speak", + { + ATTR_ENTITY_ID: "tts.google_en_com", + tts.ATTR_MEDIA_PLAYER_ENTITY_ID: "media_player.something", + tts.ATTR_MESSAGE: "There is a person at the front door.", + }, + ), + ], + indirect=["setup"], +) +async def test_tts_service( + hass: HomeAssistant, + mock_gtts: MagicMock, + calls: list[ServiceCall], + setup: str, + tts_service: str, + service_data: dict[str, Any], +) -> None: + """Test tts service.""" await hass.services.async_call( tts.DOMAIN, - "google_translate_say", - { - "entity_id": "media_player.something", - tts.ATTR_MESSAGE: "There is a person at the front door.", - }, + tts_service, + service_data, blocking=True, ) @@ -88,22 +153,43 @@ async def test_service_say(hass: HomeAssistant, mock_gtts, calls) -> None: } -async def test_service_say_german_config(hass: HomeAssistant, mock_gtts, calls) -> None: +@pytest.mark.parametrize("config", [{tts.CONF_LANG: "de"}]) +@pytest.mark.parametrize( + ("setup", "tts_service", "service_data"), + [ + ( + "mock_setup", + "google_translate_say", + { + ATTR_ENTITY_ID: "media_player.something", + tts.ATTR_MESSAGE: "There is a person at the front door.", + }, + ), + ( + "mock_config_entry_setup", + "speak", + { + ATTR_ENTITY_ID: "tts.google_de_com", + tts.ATTR_MEDIA_PLAYER_ENTITY_ID: "media_player.something", + tts.ATTR_MESSAGE: "There is a person at the front door.", + }, + ), + ], + indirect=["setup"], +) +async def test_service_say_german_config( + hass: HomeAssistant, + mock_gtts: MagicMock, + calls: list[ServiceCall], + setup: str, + tts_service: str, + service_data: dict[str, Any], +) -> None: """Test service call say with german code in the config.""" - - await async_setup_component( - hass, - tts.DOMAIN, - {tts.DOMAIN: {"platform": "google_translate", "language": "de"}}, - ) - await hass.services.async_call( tts.DOMAIN, - "google_translate_say", - { - "entity_id": "media_player.something", - tts.ATTR_MESSAGE: "There is a person at the front door.", - }, + tts_service, + service_data, blocking=True, ) @@ -117,25 +203,44 @@ async def test_service_say_german_config(hass: HomeAssistant, mock_gtts, calls) } +@pytest.mark.parametrize( + ("setup", "tts_service", "service_data"), + [ + ( + "mock_setup", + "google_translate_say", + { + ATTR_ENTITY_ID: "media_player.something", + tts.ATTR_MESSAGE: "There is a person at the front door.", + tts.ATTR_LANGUAGE: "de", + }, + ), + ( + "mock_config_entry_setup", + "speak", + { + ATTR_ENTITY_ID: "tts.google_en_com", + tts.ATTR_MEDIA_PLAYER_ENTITY_ID: "media_player.something", + tts.ATTR_MESSAGE: "There is a person at the front door.", + tts.ATTR_LANGUAGE: "de", + }, + ), + ], + indirect=["setup"], +) async def test_service_say_german_service( - hass: HomeAssistant, mock_gtts, calls + hass: HomeAssistant, + mock_gtts: MagicMock, + calls: list[ServiceCall], + setup: str, + tts_service: str, + service_data: dict[str, Any], ) -> None: """Test service call say with german code in the service.""" - - config = { - tts.DOMAIN: {"platform": "google_translate", "service_name": "google_say"} - } - - await async_setup_component(hass, tts.DOMAIN, config) - await hass.services.async_call( tts.DOMAIN, - "google_say", - { - "entity_id": "media_player.something", - tts.ATTR_MESSAGE: "There is a person at the front door.", - tts.ATTR_LANGUAGE: "de", - }, + tts_service, + service_data, blocking=True, ) @@ -149,22 +254,43 @@ async def test_service_say_german_service( } -async def test_service_say_en_uk_config(hass: HomeAssistant, mock_gtts, calls) -> None: +@pytest.mark.parametrize("config", [{tts.CONF_LANG: "en-uk"}]) +@pytest.mark.parametrize( + ("setup", "tts_service", "service_data"), + [ + ( + "mock_setup", + "google_translate_say", + { + ATTR_ENTITY_ID: "media_player.something", + tts.ATTR_MESSAGE: "There is a person at the front door.", + }, + ), + ( + "mock_config_entry_setup", + "speak", + { + ATTR_ENTITY_ID: "tts.google_en_co_uk", + tts.ATTR_MEDIA_PLAYER_ENTITY_ID: "media_player.something", + tts.ATTR_MESSAGE: "There is a person at the front door.", + }, + ), + ], + indirect=["setup"], +) +async def test_service_say_en_uk_config( + hass: HomeAssistant, + mock_gtts: MagicMock, + calls: list[ServiceCall], + setup: str, + tts_service: str, + service_data: dict[str, Any], +) -> None: """Test service call say with en-uk code in the config.""" - - await async_setup_component( - hass, - tts.DOMAIN, - {tts.DOMAIN: {"platform": "google_translate", "language": "en-uk"}}, - ) - await hass.services.async_call( tts.DOMAIN, - "google_translate_say", - { - "entity_id": "media_player.something", - tts.ATTR_MESSAGE: "There is a person at the front door.", - }, + tts_service, + service_data, blocking=True, ) @@ -178,23 +304,44 @@ async def test_service_say_en_uk_config(hass: HomeAssistant, mock_gtts, calls) - } -async def test_service_say_en_uk_service(hass: HomeAssistant, mock_gtts, calls) -> None: +@pytest.mark.parametrize( + ("setup", "tts_service", "service_data"), + [ + ( + "mock_setup", + "google_translate_say", + { + ATTR_ENTITY_ID: "media_player.something", + tts.ATTR_MESSAGE: "There is a person at the front door.", + tts.ATTR_LANGUAGE: "en-uk", + }, + ), + ( + "mock_config_entry_setup", + "speak", + { + ATTR_ENTITY_ID: "tts.google_en_com", + tts.ATTR_MEDIA_PLAYER_ENTITY_ID: "media_player.something", + tts.ATTR_MESSAGE: "There is a person at the front door.", + tts.ATTR_LANGUAGE: "en-uk", + }, + ), + ], + indirect=["setup"], +) +async def test_service_say_en_uk_service( + hass: HomeAssistant, + mock_gtts: MagicMock, + calls: list[ServiceCall], + setup: str, + tts_service: str, + service_data: dict[str, Any], +) -> None: """Test service call say with en-uk code in the config.""" - - await async_setup_component( - hass, - tts.DOMAIN, - {tts.DOMAIN: {"platform": "google_translate"}}, - ) - await hass.services.async_call( tts.DOMAIN, - "google_translate_say", - { - "entity_id": "media_player.something", - tts.ATTR_MESSAGE: "There is a person at the front door.", - tts.ATTR_LANGUAGE: "en-uk", - }, + tts_service, + service_data, blocking=True, ) @@ -208,21 +355,44 @@ async def test_service_say_en_uk_service(hass: HomeAssistant, mock_gtts, calls) } -async def test_service_say_en_couk(hass: HomeAssistant, mock_gtts, calls) -> None: +@pytest.mark.parametrize( + ("setup", "tts_service", "service_data"), + [ + ( + "mock_setup", + "google_translate_say", + { + ATTR_ENTITY_ID: "media_player.something", + tts.ATTR_MESSAGE: "There is a person at the front door.", + tts.ATTR_OPTIONS: {"tld": "co.uk"}, + }, + ), + ( + "mock_config_entry_setup", + "speak", + { + ATTR_ENTITY_ID: "tts.google_en_com", + tts.ATTR_MEDIA_PLAYER_ENTITY_ID: "media_player.something", + tts.ATTR_MESSAGE: "There is a person at the front door.", + tts.ATTR_OPTIONS: {"tld": "co.uk"}, + }, + ), + ], + indirect=["setup"], +) +async def test_service_say_en_couk( + hass: HomeAssistant, + mock_gtts: MagicMock, + calls: list[ServiceCall], + setup: str, + tts_service: str, + service_data: dict[str, Any], +) -> None: """Test service call say in co.uk tld accent.""" - - await async_setup_component( - hass, tts.DOMAIN, {tts.DOMAIN: {"platform": "google_translate"}} - ) - await hass.services.async_call( tts.DOMAIN, - "google_translate_say", - { - "entity_id": "media_player.something", - tts.ATTR_MESSAGE: "There is a person at the front door.", - tts.ATTR_OPTIONS: {"tld": "co.uk"}, - }, + tts_service, + service_data, blocking=True, ) @@ -238,20 +408,44 @@ async def test_service_say_en_couk(hass: HomeAssistant, mock_gtts, calls) -> Non } -async def test_service_say_error(hass: HomeAssistant, mock_gtts, calls) -> None: +@pytest.mark.parametrize( + ("setup", "tts_service", "service_data"), + [ + ( + "mock_setup", + "google_translate_say", + { + ATTR_ENTITY_ID: "media_player.something", + tts.ATTR_MESSAGE: "There is a person at the front door.", + }, + ), + ( + "mock_config_entry_setup", + "speak", + { + ATTR_ENTITY_ID: "tts.google_en_com", + tts.ATTR_MEDIA_PLAYER_ENTITY_ID: "media_player.something", + tts.ATTR_MESSAGE: "There is a person at the front door.", + }, + ), + ], + indirect=["setup"], +) +async def test_service_say_error( + hass: HomeAssistant, + mock_gtts: MagicMock, + calls: list[ServiceCall], + setup: str, + tts_service: str, + service_data: dict[str, Any], +) -> None: """Test service call say with http response 400.""" mock_gtts.return_value.write_to_fp.side_effect = gTTSError - await async_setup_component( - hass, tts.DOMAIN, {tts.DOMAIN: {"platform": "google_translate"}} - ) await hass.services.async_call( tts.DOMAIN, - "google_translate_say", - { - "entity_id": "media_player.something", - tts.ATTR_MESSAGE: "There is a person at the front door.", - }, + tts_service, + service_data, blocking=True, )