1140 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			1140 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			Python
		
	
	
"""The tests for the TTS component."""
 | 
						|
import asyncio
 | 
						|
from http import HTTPStatus
 | 
						|
from typing import Any
 | 
						|
from unittest.mock import patch
 | 
						|
 | 
						|
import pytest
 | 
						|
import voluptuous as vol
 | 
						|
 | 
						|
from homeassistant.components import media_source, tts
 | 
						|
from homeassistant.components.media_player import (
 | 
						|
    ATTR_MEDIA_ANNOUNCE,
 | 
						|
    ATTR_MEDIA_CONTENT_ID,
 | 
						|
    ATTR_MEDIA_CONTENT_TYPE,
 | 
						|
    DOMAIN as DOMAIN_MP,
 | 
						|
    SERVICE_PLAY_MEDIA,
 | 
						|
    MediaType,
 | 
						|
)
 | 
						|
from homeassistant.components.media_source import Unresolvable
 | 
						|
from homeassistant.components.tts.legacy import _valid_base_url
 | 
						|
from homeassistant.core import HomeAssistant
 | 
						|
from homeassistant.exceptions import HomeAssistantError
 | 
						|
from homeassistant.setup import async_setup_component
 | 
						|
from homeassistant.util.network import normalize_url
 | 
						|
 | 
						|
from .common import MockProvider, MockTTS
 | 
						|
 | 
						|
from tests.common import (
 | 
						|
    MockModule,
 | 
						|
    assert_setup_component,
 | 
						|
    async_mock_service,
 | 
						|
    mock_integration,
 | 
						|
    mock_platform,
 | 
						|
)
 | 
						|
from tests.typing import ClientSessionGenerator, WebSocketGenerator
 | 
						|
 | 
						|
ORIG_WRITE_TAGS = tts.SpeechManager.write_tags
 | 
						|
 | 
						|
 | 
						|
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, {})
 | 
						|
 | 
						|
    resolved = await media_source.async_resolve_media(hass, media_content_id, None)
 | 
						|
    return resolved.url
 | 
						|
 | 
						|
 | 
						|
@pytest.fixture
 | 
						|
def mock_provider() -> MockProvider:
 | 
						|
    """Test TTS provider."""
 | 
						|
    return MockProvider("en_US")
 | 
						|
 | 
						|
 | 
						|
@pytest.fixture
 | 
						|
async def setup_tts(hass: HomeAssistant, mock_tts: None) -> None:
 | 
						|
    """Mock TTS."""
 | 
						|
    assert await async_setup_component(hass, tts.DOMAIN, {"tts": {"platform": "test"}})
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component(hass: HomeAssistant, setup_tts) -> None:
 | 
						|
    """Set up a TTS platform with defaults."""
 | 
						|
    assert hass.services.has_service(tts.DOMAIN, "test_say")
 | 
						|
    assert hass.services.has_service(tts.DOMAIN, "clear_cache")
 | 
						|
    assert f"{tts.DOMAIN}.test" in hass.config.components
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_no_access_cache_folder(
 | 
						|
    hass: HomeAssistant, mock_init_cache_dir, mock_tts
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform with defaults."""
 | 
						|
    config = {tts.DOMAIN: {"platform": "test"}}
 | 
						|
 | 
						|
    mock_init_cache_dir.side_effect = OSError(2, "No access")
 | 
						|
    assert not await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    assert not hass.services.has_service(tts.DOMAIN, "test_say")
 | 
						|
    assert not hass.services.has_service(tts.DOMAIN, "clear_cache")
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_and_test_service(
 | 
						|
    hass: HomeAssistant, empty_cache_dir, mock_tts
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform and call service."""
 | 
						|
    calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
 | 
						|
 | 
						|
    config = {tts.DOMAIN: {"platform": "test"}}
 | 
						|
 | 
						|
    with assert_setup_component(1, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        tts.DOMAIN,
 | 
						|
        "test_say",
 | 
						|
        {
 | 
						|
            "entity_id": "media_player.something",
 | 
						|
            tts.ATTR_MESSAGE: "There is someone at the door.",
 | 
						|
        },
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
 | 
						|
    assert len(calls) == 1
 | 
						|
    assert calls[0].data[ATTR_MEDIA_ANNOUNCE] is True
 | 
						|
    assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MediaType.MUSIC
 | 
						|
    assert (
 | 
						|
        await get_media_source_url(hass, calls[0].data[ATTR_MEDIA_CONTENT_ID])
 | 
						|
        == "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3"
 | 
						|
    )
 | 
						|
    await hass.async_block_till_done()
 | 
						|
    assert (
 | 
						|
        empty_cache_dir / "42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3"
 | 
						|
    ).is_file()
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_and_test_service_with_config_language(
 | 
						|
    hass: HomeAssistant, empty_cache_dir, mock_tts
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform and call service."""
 | 
						|
    calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
 | 
						|
 | 
						|
    # Language de is matched with de_DE
 | 
						|
    config = {tts.DOMAIN: {"platform": "test", "language": "de"}}
 | 
						|
 | 
						|
    with assert_setup_component(1, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        tts.DOMAIN,
 | 
						|
        "test_say",
 | 
						|
        {
 | 
						|
            "entity_id": "media_player.something",
 | 
						|
            tts.ATTR_MESSAGE: "There is someone at the door.",
 | 
						|
        },
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    assert len(calls) == 1
 | 
						|
    assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MediaType.MUSIC
 | 
						|
    assert (
 | 
						|
        await get_media_source_url(hass, calls[0].data[ATTR_MEDIA_CONTENT_ID])
 | 
						|
        == "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_de-de_-_test.mp3"
 | 
						|
    )
 | 
						|
    await hass.async_block_till_done()
 | 
						|
    assert (
 | 
						|
        empty_cache_dir / "42f18378fd4393d18c8dd11d03fa9563c1e54491_de-de_-_test.mp3"
 | 
						|
    ).is_file()
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_and_test_service_with_config_language_special(
 | 
						|
    hass: HomeAssistant, empty_cache_dir, mock_tts
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform and call service with extend language."""
 | 
						|
    calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
 | 
						|
 | 
						|
    config = {tts.DOMAIN: {"platform": "test", "language": "en_US"}}
 | 
						|
 | 
						|
    with assert_setup_component(1, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        tts.DOMAIN,
 | 
						|
        "test_say",
 | 
						|
        {
 | 
						|
            "entity_id": "media_player.something",
 | 
						|
            tts.ATTR_MESSAGE: "There is someone at the door.",
 | 
						|
        },
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    assert len(calls) == 1
 | 
						|
    assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MediaType.MUSIC
 | 
						|
    assert (
 | 
						|
        await get_media_source_url(hass, calls[0].data[ATTR_MEDIA_CONTENT_ID])
 | 
						|
        == "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3"
 | 
						|
    )
 | 
						|
    await hass.async_block_till_done()
 | 
						|
    assert (
 | 
						|
        empty_cache_dir / "42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3"
 | 
						|
    ).is_file()
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_and_test_service_with_wrong_conf_language(
 | 
						|
    hass: HomeAssistant, mock_tts
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform and call service with wrong config."""
 | 
						|
    config = {tts.DOMAIN: {"platform": "test", "language": "ru"}}
 | 
						|
 | 
						|
    with assert_setup_component(0, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_and_test_service_with_service_language(
 | 
						|
    hass: HomeAssistant, empty_cache_dir, mock_tts
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform and call service."""
 | 
						|
    calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
 | 
						|
 | 
						|
    config = {tts.DOMAIN: {"platform": "test"}}
 | 
						|
 | 
						|
    with assert_setup_component(1, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    # Language de is matched to de_DE
 | 
						|
    await hass.services.async_call(
 | 
						|
        tts.DOMAIN,
 | 
						|
        "test_say",
 | 
						|
        {
 | 
						|
            "entity_id": "media_player.something",
 | 
						|
            tts.ATTR_MESSAGE: "There is someone at the door.",
 | 
						|
            tts.ATTR_LANGUAGE: "de",
 | 
						|
        },
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    assert len(calls) == 1
 | 
						|
    assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MediaType.MUSIC
 | 
						|
    assert (
 | 
						|
        await get_media_source_url(hass, calls[0].data[ATTR_MEDIA_CONTENT_ID])
 | 
						|
        == "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_de-de_-_test.mp3"
 | 
						|
    )
 | 
						|
    await hass.async_block_till_done()
 | 
						|
    assert (
 | 
						|
        empty_cache_dir / "42f18378fd4393d18c8dd11d03fa9563c1e54491_de-de_-_test.mp3"
 | 
						|
    ).is_file()
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_test_service_with_wrong_service_language(
 | 
						|
    hass: HomeAssistant, empty_cache_dir, mock_tts
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform and call service."""
 | 
						|
    calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
 | 
						|
 | 
						|
    config = {tts.DOMAIN: {"platform": "test"}}
 | 
						|
 | 
						|
    with assert_setup_component(1, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    with pytest.raises(HomeAssistantError):
 | 
						|
        await hass.services.async_call(
 | 
						|
            tts.DOMAIN,
 | 
						|
            "test_say",
 | 
						|
            {
 | 
						|
                "entity_id": "media_player.something",
 | 
						|
                tts.ATTR_MESSAGE: "There is someone at the door.",
 | 
						|
                tts.ATTR_LANGUAGE: "lang",
 | 
						|
            },
 | 
						|
            blocking=True,
 | 
						|
        )
 | 
						|
    assert len(calls) == 0
 | 
						|
    assert not (
 | 
						|
        empty_cache_dir / "42f18378fd4393d18c8dd11d03fa9563c1e54491_lang_-_test.mp3"
 | 
						|
    ).is_file()
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_and_test_service_with_service_options(
 | 
						|
    hass: HomeAssistant, empty_cache_dir, mock_tts
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform and call service with options."""
 | 
						|
    calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
 | 
						|
 | 
						|
    config = {tts.DOMAIN: {"platform": "test"}}
 | 
						|
 | 
						|
    with assert_setup_component(1, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    # Language de is matched with de_DE
 | 
						|
    await hass.services.async_call(
 | 
						|
        tts.DOMAIN,
 | 
						|
        "test_say",
 | 
						|
        {
 | 
						|
            "entity_id": "media_player.something",
 | 
						|
            tts.ATTR_MESSAGE: "There is someone at the door.",
 | 
						|
            tts.ATTR_LANGUAGE: "de",
 | 
						|
            tts.ATTR_OPTIONS: {"voice": "alex", "age": 5},
 | 
						|
        },
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    opt_hash = tts._hash_options({"voice": "alex", "age": 5})
 | 
						|
 | 
						|
    assert len(calls) == 1
 | 
						|
    assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MediaType.MUSIC
 | 
						|
    assert (
 | 
						|
        await get_media_source_url(hass, calls[0].data[ATTR_MEDIA_CONTENT_ID])
 | 
						|
        == f"/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_de-de_{opt_hash}_test.mp3"
 | 
						|
    )
 | 
						|
    await hass.async_block_till_done()
 | 
						|
    assert (
 | 
						|
        empty_cache_dir
 | 
						|
        / f"42f18378fd4393d18c8dd11d03fa9563c1e54491_de-de_{opt_hash}_test.mp3"
 | 
						|
    ).is_file()
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_and_test_with_service_options_def(
 | 
						|
    hass: HomeAssistant, empty_cache_dir
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform and call service with default options."""
 | 
						|
    calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
 | 
						|
 | 
						|
    config = {tts.DOMAIN: {"platform": "test"}}
 | 
						|
 | 
						|
    class MockProviderWithDefaults(MockProvider):
 | 
						|
        """Mock provider with default options."""
 | 
						|
 | 
						|
        @property
 | 
						|
        def default_options(self):
 | 
						|
            return {"voice": "alex"}
 | 
						|
 | 
						|
    mock_integration(hass, MockModule(domain="test"))
 | 
						|
    mock_platform(hass, "test.tts", MockTTS(MockProviderWithDefaults))
 | 
						|
 | 
						|
    with assert_setup_component(1, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
        # Language de is matched with de_DE
 | 
						|
        await hass.services.async_call(
 | 
						|
            tts.DOMAIN,
 | 
						|
            "test_say",
 | 
						|
            {
 | 
						|
                "entity_id": "media_player.something",
 | 
						|
                tts.ATTR_MESSAGE: "There is someone at the door.",
 | 
						|
                tts.ATTR_LANGUAGE: "de",
 | 
						|
            },
 | 
						|
            blocking=True,
 | 
						|
        )
 | 
						|
        opt_hash = tts._hash_options({"voice": "alex"})
 | 
						|
 | 
						|
        assert len(calls) == 1
 | 
						|
        assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MediaType.MUSIC
 | 
						|
        assert (
 | 
						|
            await get_media_source_url(hass, calls[0].data[ATTR_MEDIA_CONTENT_ID])
 | 
						|
            == f"/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_de-de_{opt_hash}_test.mp3"
 | 
						|
        )
 | 
						|
        await hass.async_block_till_done()
 | 
						|
        assert (
 | 
						|
            empty_cache_dir
 | 
						|
            / f"42f18378fd4393d18c8dd11d03fa9563c1e54491_de-de_{opt_hash}_test.mp3"
 | 
						|
        ).is_file()
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_and_test_with_service_options_def_2(
 | 
						|
    hass: HomeAssistant, empty_cache_dir
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform and call service with default options.
 | 
						|
 | 
						|
    This tests merging default and user provided options.
 | 
						|
    """
 | 
						|
    calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
 | 
						|
 | 
						|
    config = {tts.DOMAIN: {"platform": "test"}}
 | 
						|
 | 
						|
    class MockProviderWithDefaults(MockProvider):
 | 
						|
        """Mock provider with default options."""
 | 
						|
 | 
						|
        @property
 | 
						|
        def default_options(self):
 | 
						|
            return {"voice": "alex"}
 | 
						|
 | 
						|
    mock_integration(hass, MockModule(domain="test"))
 | 
						|
    mock_platform(hass, "test.tts", MockTTS(MockProviderWithDefaults))
 | 
						|
 | 
						|
    with assert_setup_component(1, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
        # Language de is matched with de_DE
 | 
						|
        await hass.services.async_call(
 | 
						|
            tts.DOMAIN,
 | 
						|
            "test_say",
 | 
						|
            {
 | 
						|
                "entity_id": "media_player.something",
 | 
						|
                tts.ATTR_MESSAGE: "There is someone at the door.",
 | 
						|
                tts.ATTR_LANGUAGE: "de",
 | 
						|
                tts.ATTR_OPTIONS: {"age": 5},
 | 
						|
            },
 | 
						|
            blocking=True,
 | 
						|
        )
 | 
						|
        opt_hash = tts._hash_options({"voice": "alex", "age": 5})
 | 
						|
 | 
						|
        assert len(calls) == 1
 | 
						|
        assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MediaType.MUSIC
 | 
						|
        assert (
 | 
						|
            await get_media_source_url(hass, calls[0].data[ATTR_MEDIA_CONTENT_ID])
 | 
						|
            == f"/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_de-de_{opt_hash}_test.mp3"
 | 
						|
        )
 | 
						|
        await hass.async_block_till_done()
 | 
						|
        assert (
 | 
						|
            empty_cache_dir
 | 
						|
            / f"42f18378fd4393d18c8dd11d03fa9563c1e54491_de-de_{opt_hash}_test.mp3"
 | 
						|
        ).is_file()
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_and_test_service_with_service_options_wrong(
 | 
						|
    hass: HomeAssistant, empty_cache_dir, mock_tts
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform and call service with wrong options."""
 | 
						|
    calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
 | 
						|
 | 
						|
    config = {tts.DOMAIN: {"platform": "test"}}
 | 
						|
 | 
						|
    with assert_setup_component(1, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    # Language de is matched with de_DE
 | 
						|
    with pytest.raises(HomeAssistantError):
 | 
						|
        await hass.services.async_call(
 | 
						|
            tts.DOMAIN,
 | 
						|
            "test_say",
 | 
						|
            {
 | 
						|
                "entity_id": "media_player.something",
 | 
						|
                tts.ATTR_MESSAGE: "There is someone at the door.",
 | 
						|
                tts.ATTR_LANGUAGE: "de",
 | 
						|
                tts.ATTR_OPTIONS: {"speed": 1},
 | 
						|
            },
 | 
						|
            blocking=True,
 | 
						|
        )
 | 
						|
    opt_hash = tts._hash_options({"speed": 1})
 | 
						|
 | 
						|
    assert len(calls) == 0
 | 
						|
    await hass.async_block_till_done()
 | 
						|
    assert not (
 | 
						|
        empty_cache_dir
 | 
						|
        / f"42f18378fd4393d18c8dd11d03fa9563c1e54491_de-de_{opt_hash}_test.mp3"
 | 
						|
    ).is_file()
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_and_test_service_with_base_url_set(
 | 
						|
    hass: HomeAssistant, mock_tts
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform with ``base_url`` set and call service."""
 | 
						|
    calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
 | 
						|
 | 
						|
    config = {tts.DOMAIN: {"platform": "test", "base_url": "http://fnord"}}
 | 
						|
 | 
						|
    with assert_setup_component(1, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        tts.DOMAIN,
 | 
						|
        "test_say",
 | 
						|
        {
 | 
						|
            "entity_id": "media_player.something",
 | 
						|
            tts.ATTR_MESSAGE: "There is someone at the door.",
 | 
						|
        },
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    assert len(calls) == 1
 | 
						|
    assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MediaType.MUSIC
 | 
						|
    assert (
 | 
						|
        await get_media_source_url(hass, calls[0].data[ATTR_MEDIA_CONTENT_ID])
 | 
						|
        == "http://fnord"
 | 
						|
        "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491"
 | 
						|
        "_en-us_-_test.mp3"
 | 
						|
    )
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_and_test_service_clear_cache(
 | 
						|
    hass: HomeAssistant, empty_cache_dir, mock_tts
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform and call service clear cache."""
 | 
						|
    calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
 | 
						|
 | 
						|
    config = {tts.DOMAIN: {"platform": "test"}}
 | 
						|
 | 
						|
    with assert_setup_component(1, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        tts.DOMAIN,
 | 
						|
        "test_say",
 | 
						|
        {
 | 
						|
            "entity_id": "media_player.something",
 | 
						|
            tts.ATTR_MESSAGE: "There is someone at the door.",
 | 
						|
        },
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    # To make sure the file is persisted
 | 
						|
    assert len(calls) == 1
 | 
						|
    await get_media_source_url(hass, calls[0].data[ATTR_MEDIA_CONTENT_ID])
 | 
						|
    await hass.async_block_till_done()
 | 
						|
    assert (
 | 
						|
        empty_cache_dir / "42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3"
 | 
						|
    ).is_file()
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        tts.DOMAIN, tts.SERVICE_CLEAR_CACHE, {}, blocking=True
 | 
						|
    )
 | 
						|
 | 
						|
    assert not (
 | 
						|
        empty_cache_dir / "42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3"
 | 
						|
    ).is_file()
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_and_test_service_with_receive_voice(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    mock_provider: MockProvider,
 | 
						|
    hass_client: ClientSessionGenerator,
 | 
						|
    mock_tts,
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform and call service and receive voice."""
 | 
						|
    calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
 | 
						|
 | 
						|
    config = {tts.DOMAIN: {"platform": "test"}}
 | 
						|
 | 
						|
    with assert_setup_component(1, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    message = "There is someone at the door."
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        tts.DOMAIN,
 | 
						|
        "test_say",
 | 
						|
        {
 | 
						|
            "entity_id": "media_player.something",
 | 
						|
            tts.ATTR_MESSAGE: message,
 | 
						|
        },
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    assert len(calls) == 1
 | 
						|
 | 
						|
    url = await get_media_source_url(hass, calls[0].data[ATTR_MEDIA_CONTENT_ID])
 | 
						|
    client = await hass_client()
 | 
						|
    req = await client.get(url)
 | 
						|
    # Language en is matched with en_US
 | 
						|
    _, tts_data = mock_provider.get_tts_audio("bla", "en")
 | 
						|
    assert tts_data is not None
 | 
						|
    tts_data = tts.SpeechManager.write_tags(
 | 
						|
        "42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3",
 | 
						|
        tts_data,
 | 
						|
        mock_provider,
 | 
						|
        message,
 | 
						|
        "en",
 | 
						|
        None,
 | 
						|
    )
 | 
						|
    assert req.status == HTTPStatus.OK
 | 
						|
    assert await req.read() == tts_data
 | 
						|
 | 
						|
    extension, data = await tts.async_get_media_source_audio(
 | 
						|
        hass, calls[0].data[ATTR_MEDIA_CONTENT_ID]
 | 
						|
    )
 | 
						|
    assert extension == "mp3"
 | 
						|
    assert tts_data == data
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_and_test_service_with_receive_voice_german(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    mock_provider: MockProvider,
 | 
						|
    hass_client: ClientSessionGenerator,
 | 
						|
    mock_tts,
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform and call service and receive voice."""
 | 
						|
    calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
 | 
						|
 | 
						|
    # Language de is matched with de_DE
 | 
						|
    config = {tts.DOMAIN: {"platform": "test", "language": "de"}}
 | 
						|
 | 
						|
    with assert_setup_component(1, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        tts.DOMAIN,
 | 
						|
        "test_say",
 | 
						|
        {
 | 
						|
            "entity_id": "media_player.something",
 | 
						|
            tts.ATTR_MESSAGE: "There is someone at the door.",
 | 
						|
        },
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    assert len(calls) == 1
 | 
						|
    url = await get_media_source_url(hass, calls[0].data[ATTR_MEDIA_CONTENT_ID])
 | 
						|
    client = await hass_client()
 | 
						|
    req = await client.get(url)
 | 
						|
    _, tts_data = mock_provider.get_tts_audio("bla", "de")
 | 
						|
    assert tts_data is not None
 | 
						|
    tts_data = tts.SpeechManager.write_tags(
 | 
						|
        "42f18378fd4393d18c8dd11d03fa9563c1e54491_de-de_-_test.mp3",
 | 
						|
        tts_data,
 | 
						|
        mock_provider,
 | 
						|
        "There is someone at the door.",
 | 
						|
        "de",
 | 
						|
        None,
 | 
						|
    )
 | 
						|
    assert req.status == HTTPStatus.OK
 | 
						|
    assert await req.read() == tts_data
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_and_web_view_wrong_file(
 | 
						|
    hass: HomeAssistant, hass_client: ClientSessionGenerator, mock_tts
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform and receive wrong file from web."""
 | 
						|
    config = {tts.DOMAIN: {"platform": "test"}}
 | 
						|
 | 
						|
    with assert_setup_component(1, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    client = await hass_client()
 | 
						|
 | 
						|
    url = "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3"
 | 
						|
 | 
						|
    req = await client.get(url)
 | 
						|
    assert req.status == HTTPStatus.NOT_FOUND
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_and_web_view_wrong_filename(
 | 
						|
    hass: HomeAssistant, hass_client: ClientSessionGenerator, mock_tts
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform and receive wrong filename from web."""
 | 
						|
    config = {tts.DOMAIN: {"platform": "test"}}
 | 
						|
 | 
						|
    with assert_setup_component(1, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    client = await hass_client()
 | 
						|
 | 
						|
    url = "/api/tts_proxy/265944dsk32c1b2a621be5930510bb2cd_en-us_-_test.mp3"
 | 
						|
 | 
						|
    req = await client.get(url)
 | 
						|
    assert req.status == HTTPStatus.NOT_FOUND
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_test_without_cache(
 | 
						|
    hass: HomeAssistant, empty_cache_dir, mock_tts
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform without cache."""
 | 
						|
    calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
 | 
						|
 | 
						|
    config = {tts.DOMAIN: {"platform": "test", "cache": False}}
 | 
						|
 | 
						|
    with assert_setup_component(1, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        tts.DOMAIN,
 | 
						|
        "test_say",
 | 
						|
        {
 | 
						|
            "entity_id": "media_player.something",
 | 
						|
            tts.ATTR_MESSAGE: "There is someone at the door.",
 | 
						|
        },
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    assert len(calls) == 1
 | 
						|
    await hass.async_block_till_done()
 | 
						|
    assert not (
 | 
						|
        empty_cache_dir / "42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3"
 | 
						|
    ).is_file()
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_test_with_cache_call_service_without_cache(
 | 
						|
    hass: HomeAssistant, empty_cache_dir, mock_tts
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform with cache and call service without cache."""
 | 
						|
    calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
 | 
						|
 | 
						|
    config = {tts.DOMAIN: {"platform": "test", "cache": True}}
 | 
						|
 | 
						|
    with assert_setup_component(1, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        tts.DOMAIN,
 | 
						|
        "test_say",
 | 
						|
        {
 | 
						|
            "entity_id": "media_player.something",
 | 
						|
            tts.ATTR_MESSAGE: "There is someone at the door.",
 | 
						|
            tts.ATTR_CACHE: False,
 | 
						|
        },
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    assert len(calls) == 1
 | 
						|
    await hass.async_block_till_done()
 | 
						|
    assert not (
 | 
						|
        empty_cache_dir / "42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3"
 | 
						|
    ).is_file()
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_test_with_cache_dir(
 | 
						|
    hass: HomeAssistant, empty_cache_dir, mock_provider: MockProvider
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform with cache and call service without cache."""
 | 
						|
    calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
 | 
						|
 | 
						|
    # Language en is matched with en_US
 | 
						|
    _, tts_data = mock_provider.get_tts_audio("bla", "en")
 | 
						|
    assert tts_data is not None
 | 
						|
    cache_file = (
 | 
						|
        empty_cache_dir / "42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3"
 | 
						|
    )
 | 
						|
 | 
						|
    with open(cache_file, "wb") as voice_file:
 | 
						|
        voice_file.write(tts_data)
 | 
						|
 | 
						|
    config = {tts.DOMAIN: {"platform": "test", "cache": True}}
 | 
						|
 | 
						|
    class MockProviderBoom(MockProvider):
 | 
						|
        """Mock provider that blows up."""
 | 
						|
 | 
						|
        def get_tts_audio(
 | 
						|
            self, message: str, language: str, options: dict[str, Any] | None = None
 | 
						|
        ) -> tts.TtsAudioType:
 | 
						|
            """Load TTS dat."""
 | 
						|
            # This should not be called, data should be fetched from cache
 | 
						|
            raise Exception("Boom!")  # pylint: disable=broad-exception-raised
 | 
						|
 | 
						|
    mock_integration(hass, MockModule(domain="test"))
 | 
						|
    mock_platform(hass, "test.tts", MockTTS(MockProviderBoom))
 | 
						|
 | 
						|
    with assert_setup_component(1, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        tts.DOMAIN,
 | 
						|
        "test_say",
 | 
						|
        {
 | 
						|
            "entity_id": "media_player.something",
 | 
						|
            tts.ATTR_MESSAGE: "There is someone at the door.",
 | 
						|
        },
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    assert len(calls) == 1
 | 
						|
    assert (
 | 
						|
        await get_media_source_url(hass, calls[0].data[ATTR_MEDIA_CONTENT_ID])
 | 
						|
        == "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3"
 | 
						|
    )
 | 
						|
    await hass.async_block_till_done()
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_test_with_error_on_get_tts(hass: HomeAssistant) -> None:
 | 
						|
    """Set up a TTS platform with wrong get_tts_audio."""
 | 
						|
    calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
 | 
						|
 | 
						|
    config = {tts.DOMAIN: {"platform": "test"}}
 | 
						|
 | 
						|
    class MockProviderEmpty(MockProvider):
 | 
						|
        """Mock provider with empty get_tts_audio."""
 | 
						|
 | 
						|
        def get_tts_audio(
 | 
						|
            self, message: str, language: str, options: dict[str, Any] | None = None
 | 
						|
        ) -> tts.TtsAudioType:
 | 
						|
            """Load TTS dat."""
 | 
						|
            return (None, None)
 | 
						|
 | 
						|
    mock_integration(hass, MockModule(domain="test"))
 | 
						|
    mock_platform(hass, "test.tts", MockTTS(MockProviderEmpty))
 | 
						|
 | 
						|
    with assert_setup_component(1, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        tts.DOMAIN,
 | 
						|
        "test_say",
 | 
						|
        {
 | 
						|
            "entity_id": "media_player.something",
 | 
						|
            tts.ATTR_MESSAGE: "There is someone at the door.",
 | 
						|
        },
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    assert len(calls) == 1
 | 
						|
    with pytest.raises(Unresolvable):
 | 
						|
        await get_media_source_url(hass, calls[0].data[ATTR_MEDIA_CONTENT_ID])
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_load_cache_retrieve_without_mem_cache(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    mock_provider: MockProvider,
 | 
						|
    empty_cache_dir,
 | 
						|
    hass_client: ClientSessionGenerator,
 | 
						|
    mock_tts,
 | 
						|
) -> None:
 | 
						|
    """Set up component and load cache and get without mem cache."""
 | 
						|
    # Language en is matched with en_US
 | 
						|
    _, tts_data = mock_provider.get_tts_audio("bla", "en")
 | 
						|
    assert tts_data is not None
 | 
						|
    cache_file = (
 | 
						|
        empty_cache_dir / "42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3"
 | 
						|
    )
 | 
						|
 | 
						|
    with open(cache_file, "wb") as voice_file:
 | 
						|
        voice_file.write(tts_data)
 | 
						|
 | 
						|
    config = {tts.DOMAIN: {"platform": "test", "cache": True}}
 | 
						|
 | 
						|
    with assert_setup_component(1, tts.DOMAIN):
 | 
						|
        assert await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    client = await hass_client()
 | 
						|
 | 
						|
    url = "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3"
 | 
						|
 | 
						|
    req = await client.get(url)
 | 
						|
    assert req.status == HTTPStatus.OK
 | 
						|
    assert await req.read() == tts_data
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_and_web_get_url(
 | 
						|
    hass: HomeAssistant, hass_client: ClientSessionGenerator, mock_tts
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform and receive file from web."""
 | 
						|
    config = {tts.DOMAIN: {"platform": "test"}}
 | 
						|
 | 
						|
    await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    client = await hass_client()
 | 
						|
 | 
						|
    url = "/api/tts_get_url"
 | 
						|
    data = {"platform": "test", "message": "There is someone at the door."}
 | 
						|
 | 
						|
    req = await client.post(url, json=data)
 | 
						|
    assert req.status == HTTPStatus.OK
 | 
						|
    response = await req.json()
 | 
						|
    assert response == {
 | 
						|
        "url": "http://example.local:8123/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3",
 | 
						|
        "path": "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3",
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
async def test_setup_component_and_web_get_url_bad_config(
 | 
						|
    hass: HomeAssistant, hass_client: ClientSessionGenerator, mock_tts
 | 
						|
) -> None:
 | 
						|
    """Set up a TTS platform and receive wrong file from web."""
 | 
						|
    config = {tts.DOMAIN: {"platform": "test"}}
 | 
						|
 | 
						|
    await async_setup_component(hass, tts.DOMAIN, config)
 | 
						|
 | 
						|
    client = await hass_client()
 | 
						|
 | 
						|
    url = "/api/tts_get_url"
 | 
						|
    data = {"message": "There is someone at the door."}
 | 
						|
 | 
						|
    req = await client.post(url, json=data)
 | 
						|
    assert req.status == HTTPStatus.BAD_REQUEST
 | 
						|
 | 
						|
 | 
						|
async def test_tags_with_wave(hass: HomeAssistant, mock_provider: MockProvider) -> None:
 | 
						|
    """Set up a TTS platform and call service and receive voice."""
 | 
						|
 | 
						|
    # below data represents an empty wav file
 | 
						|
    tts_data = bytes.fromhex(
 | 
						|
        "52 49 46 46 24 00 00 00 57 41 56 45 66 6d 74 20 10 00 00 00 01 00 02 00"
 | 
						|
        + "22 56 00 00 88 58 01 00 04 00 10 00 64 61 74 61 00 00 00 00"
 | 
						|
    )
 | 
						|
 | 
						|
    tagged_data = ORIG_WRITE_TAGS(
 | 
						|
        "42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.wav",
 | 
						|
        tts_data,
 | 
						|
        mock_provider,
 | 
						|
        "AI person is in front of your door.",
 | 
						|
        "en",
 | 
						|
        None,
 | 
						|
    )
 | 
						|
 | 
						|
    assert tagged_data != tts_data
 | 
						|
 | 
						|
 | 
						|
@pytest.mark.parametrize(
 | 
						|
    "value",
 | 
						|
    (
 | 
						|
        "http://example.local:8123",
 | 
						|
        "http://example.local",
 | 
						|
        "http://example.local:80",
 | 
						|
        "https://example.com",
 | 
						|
        "https://example.com:443",
 | 
						|
        "https://example.com:8123",
 | 
						|
    ),
 | 
						|
)
 | 
						|
def test_valid_base_url(value) -> None:
 | 
						|
    """Test we validate base urls."""
 | 
						|
    assert _valid_base_url(value) == normalize_url(value)
 | 
						|
    # Test we strip trailing `/`
 | 
						|
    assert _valid_base_url(value + "/") == normalize_url(value)
 | 
						|
 | 
						|
 | 
						|
@pytest.mark.parametrize(
 | 
						|
    "value",
 | 
						|
    (
 | 
						|
        "http://example.local:8123/sub-path",
 | 
						|
        "http://example.local/sub-path",
 | 
						|
        "https://example.com/sub-path",
 | 
						|
        "https://example.com:8123/sub-path",
 | 
						|
        "mailto:some@email",
 | 
						|
        "http:example.com",
 | 
						|
        "http:/example.com",
 | 
						|
        "http//example.com",
 | 
						|
        "example.com",
 | 
						|
    ),
 | 
						|
)
 | 
						|
def test_invalid_base_url(value) -> None:
 | 
						|
    """Test we catch bad base urls."""
 | 
						|
    with pytest.raises(vol.Invalid):
 | 
						|
        _valid_base_url(value)
 | 
						|
 | 
						|
 | 
						|
@pytest.mark.parametrize(
 | 
						|
    ("engine", "language", "options", "cache", "result_engine", "result_query"),
 | 
						|
    (
 | 
						|
        (None, None, None, None, "test", ""),
 | 
						|
        (None, "de", None, None, "test", "language=de"),
 | 
						|
        (None, "de", {"voice": "henk"}, None, "test", "language=de&voice=henk"),
 | 
						|
        (None, "de", None, True, "test", "cache=true&language=de"),
 | 
						|
    ),
 | 
						|
)
 | 
						|
async def test_generate_media_source_id(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    setup_tts,
 | 
						|
    engine,
 | 
						|
    language,
 | 
						|
    options,
 | 
						|
    cache,
 | 
						|
    result_engine,
 | 
						|
    result_query,
 | 
						|
) -> None:
 | 
						|
    """Test generating a media source ID."""
 | 
						|
    media_source_id = tts.generate_media_source_id(
 | 
						|
        hass, "msg", engine, language, options, cache
 | 
						|
    )
 | 
						|
 | 
						|
    assert media_source_id.startswith("media-source://tts/")
 | 
						|
    _, _, engine_query = media_source_id.rpartition("/")
 | 
						|
    engine, _, query = engine_query.partition("?")
 | 
						|
    assert engine == result_engine
 | 
						|
    assert query.startswith("message=msg")
 | 
						|
    assert query[12:] == result_query
 | 
						|
 | 
						|
 | 
						|
@pytest.mark.parametrize(
 | 
						|
    ("engine", "language", "options"),
 | 
						|
    (
 | 
						|
        ("not-loaded-engine", None, None),
 | 
						|
        (None, "unsupported-language", None),
 | 
						|
        (None, None, {"option": "not-supported"}),
 | 
						|
    ),
 | 
						|
)
 | 
						|
async def test_generate_media_source_id_invalid_options(
 | 
						|
    hass: HomeAssistant, setup_tts, engine, language, options
 | 
						|
) -> None:
 | 
						|
    """Test generating a media source ID."""
 | 
						|
    with pytest.raises(HomeAssistantError):
 | 
						|
        tts.generate_media_source_id(hass, "msg", engine, language, options, None)
 | 
						|
 | 
						|
 | 
						|
def test_resolve_engine(hass: HomeAssistant, setup_tts) -> None:
 | 
						|
    """Test resolving engine."""
 | 
						|
    assert tts.async_resolve_engine(hass, None) == "test"
 | 
						|
    assert tts.async_resolve_engine(hass, "test") == "test"
 | 
						|
    assert tts.async_resolve_engine(hass, "non-existing") is None
 | 
						|
 | 
						|
    with patch.dict(hass.data[tts.DOMAIN].providers, {}, clear=True):
 | 
						|
        assert tts.async_resolve_engine(hass, "test") is None
 | 
						|
 | 
						|
    with patch.dict(hass.data[tts.DOMAIN].providers, {"cloud": object()}):
 | 
						|
        assert tts.async_resolve_engine(hass, None) == "cloud"
 | 
						|
 | 
						|
 | 
						|
async def test_support_options(hass: HomeAssistant, setup_tts) -> None:
 | 
						|
    """Test supporting options."""
 | 
						|
    # Language en is matched with en_US
 | 
						|
    assert await tts.async_support_options(hass, "test", "en") is True
 | 
						|
    assert await tts.async_support_options(hass, "test", "nl") is False
 | 
						|
    assert (
 | 
						|
        await tts.async_support_options(hass, "test", "en", {"invalid_option": "yo"})
 | 
						|
        is False
 | 
						|
    )
 | 
						|
 | 
						|
 | 
						|
async def test_fetching_in_async(
 | 
						|
    hass: HomeAssistant, hass_client: ClientSessionGenerator
 | 
						|
) -> None:
 | 
						|
    """Test async fetching of data."""
 | 
						|
    tts_audio: asyncio.Future[bytes] = asyncio.Future()
 | 
						|
 | 
						|
    class ProviderWithAsyncFetching(MockProvider):
 | 
						|
        """Provider that supports audio output option."""
 | 
						|
 | 
						|
        @property
 | 
						|
        def supported_options(self) -> list[str]:
 | 
						|
            """Return list of supported options like voice, emotions."""
 | 
						|
            return [tts.ATTR_AUDIO_OUTPUT]
 | 
						|
 | 
						|
        @property
 | 
						|
        def default_options(self) -> dict[str, str]:
 | 
						|
            """Return a dict including the default options."""
 | 
						|
            return {tts.ATTR_AUDIO_OUTPUT: "mp3"}
 | 
						|
 | 
						|
        async def async_get_tts_audio(
 | 
						|
            self, message: str, language: str, options: dict[str, Any] | None = None
 | 
						|
        ) -> tts.TtsAudioType:
 | 
						|
            return ("mp3", await tts_audio)
 | 
						|
 | 
						|
    mock_integration(hass, MockModule(domain="test"))
 | 
						|
    mock_platform(hass, "test.tts", MockTTS(ProviderWithAsyncFetching))
 | 
						|
    assert await async_setup_component(hass, tts.DOMAIN, {"tts": {"platform": "test"}})
 | 
						|
 | 
						|
    # Test async_get_media_source_audio
 | 
						|
    media_source_id = tts.generate_media_source_id(
 | 
						|
        hass, "test message", "test", "en", None, None
 | 
						|
    )
 | 
						|
 | 
						|
    task = hass.async_create_task(
 | 
						|
        tts.async_get_media_source_audio(hass, media_source_id)
 | 
						|
    )
 | 
						|
    task2 = hass.async_create_task(
 | 
						|
        tts.async_get_media_source_audio(hass, media_source_id)
 | 
						|
    )
 | 
						|
 | 
						|
    url = await get_media_source_url(hass, media_source_id)
 | 
						|
    client = await hass_client()
 | 
						|
    client_get_task = hass.async_create_task(client.get(url))
 | 
						|
 | 
						|
    # Make sure that tasks are waiting for our future to resolve
 | 
						|
    done, pending = await asyncio.wait((task, task2, client_get_task), timeout=0.1)
 | 
						|
    assert len(done) == 0
 | 
						|
    assert len(pending) == 3
 | 
						|
 | 
						|
    tts_audio.set_result(b"test")
 | 
						|
 | 
						|
    assert await task == ("mp3", b"test")
 | 
						|
    assert await task2 == ("mp3", b"test")
 | 
						|
 | 
						|
    req = await client_get_task
 | 
						|
    assert req.status == HTTPStatus.OK
 | 
						|
    assert await req.read() == b"test"
 | 
						|
 | 
						|
    # Test error is not cached
 | 
						|
    media_source_id = tts.generate_media_source_id(
 | 
						|
        hass, "test message 2", "test", "en", None, None
 | 
						|
    )
 | 
						|
    tts_audio = asyncio.Future()
 | 
						|
    tts_audio.set_exception(HomeAssistantError("test error"))
 | 
						|
    with pytest.raises(HomeAssistantError):
 | 
						|
        assert await tts.async_get_media_source_audio(hass, media_source_id)
 | 
						|
 | 
						|
    tts_audio = asyncio.Future()
 | 
						|
    tts_audio.set_result(b"test 2")
 | 
						|
    assert await tts.async_get_media_source_audio(hass, media_source_id) == (
 | 
						|
        "mp3",
 | 
						|
        b"test 2",
 | 
						|
    )
 | 
						|
 | 
						|
 | 
						|
async def test_ws_list_engines(
 | 
						|
    hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_tts
 | 
						|
) -> None:
 | 
						|
    """Test streaming audio and getting response."""
 | 
						|
    client = await hass_ws_client()
 | 
						|
 | 
						|
    await client.send_json_auto_id({"type": "tts/engine/list"})
 | 
						|
 | 
						|
    msg = await client.receive_json()
 | 
						|
    assert msg["success"]
 | 
						|
    assert msg["result"] == {
 | 
						|
        "providers": [
 | 
						|
            {
 | 
						|
                "engine_id": "test",
 | 
						|
                "supported_languages": ["de_CH", "de_DE", "en_GB", "en_US"],
 | 
						|
            }
 | 
						|
        ]
 | 
						|
    }
 | 
						|
 | 
						|
    await client.send_json_auto_id({"type": "tts/engine/list", "language": "smurfish"})
 | 
						|
 | 
						|
    msg = await client.receive_json()
 | 
						|
    assert msg["success"]
 | 
						|
    assert msg["result"] == {
 | 
						|
        "providers": [{"engine_id": "test", "supported_languages": []}]
 | 
						|
    }
 | 
						|
 | 
						|
    await client.send_json_auto_id({"type": "tts/engine/list", "language": "en"})
 | 
						|
 | 
						|
    msg = await client.receive_json()
 | 
						|
    assert msg["success"]
 | 
						|
    assert msg["result"] == {
 | 
						|
        "providers": [{"engine_id": "test", "supported_languages": ["en_US", "en_GB"]}]
 | 
						|
    }
 | 
						|
 | 
						|
    await client.send_json_auto_id({"type": "tts/engine/list", "language": "en-UK"})
 | 
						|
 | 
						|
    msg = await client.receive_json()
 | 
						|
    assert msg["success"]
 | 
						|
    assert msg["result"] == {
 | 
						|
        "providers": [{"engine_id": "test", "supported_languages": ["en_GB", "en_US"]}]
 | 
						|
    }
 | 
						|
 | 
						|
    await client.send_json_auto_id({"type": "tts/engine/list", "language": "de"})
 | 
						|
    msg = await client.receive_json()
 | 
						|
    assert msg["type"] == "result"
 | 
						|
    assert msg["success"]
 | 
						|
    assert msg["result"] == {
 | 
						|
        "providers": [{"engine_id": "test", "supported_languages": ["de_DE", "de_CH"]}]
 | 
						|
    }
 | 
						|
 | 
						|
    await client.send_json_auto_id(
 | 
						|
        {"type": "tts/engine/list", "language": "de", "country": "ch"}
 | 
						|
    )
 | 
						|
    msg = await client.receive_json()
 | 
						|
    assert msg["type"] == "result"
 | 
						|
    assert msg["success"]
 | 
						|
    assert msg["result"] == {
 | 
						|
        "providers": [{"engine_id": "test", "supported_languages": ["de_CH", "de_DE"]}]
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
async def test_ws_list_voices(
 | 
						|
    hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_tts
 | 
						|
) -> None:
 | 
						|
    """Test streaming audio and getting response."""
 | 
						|
    client = await hass_ws_client()
 | 
						|
 | 
						|
    await client.send_json_auto_id(
 | 
						|
        {
 | 
						|
            "type": "tts/engine/voices",
 | 
						|
            "engine_id": "smurf_tts",
 | 
						|
            "language": "smurfish",
 | 
						|
        }
 | 
						|
    )
 | 
						|
 | 
						|
    msg = await client.receive_json()
 | 
						|
    assert not msg["success"]
 | 
						|
    assert msg["error"] == {
 | 
						|
        "code": "not_found",
 | 
						|
        "message": "tts engine smurf_tts not found",
 | 
						|
    }
 | 
						|
 | 
						|
    await client.send_json_auto_id(
 | 
						|
        {
 | 
						|
            "type": "tts/engine/voices",
 | 
						|
            "engine_id": "test",
 | 
						|
            "language": "smurfish",
 | 
						|
        }
 | 
						|
    )
 | 
						|
 | 
						|
    msg = await client.receive_json()
 | 
						|
    assert msg["success"]
 | 
						|
    assert msg["result"] == {"voices": None}
 | 
						|
 | 
						|
    await client.send_json_auto_id(
 | 
						|
        {
 | 
						|
            "type": "tts/engine/voices",
 | 
						|
            "engine_id": "test",
 | 
						|
            "language": "en-US",
 | 
						|
        }
 | 
						|
    )
 | 
						|
 | 
						|
    msg = await client.receive_json()
 | 
						|
    assert msg["success"]
 | 
						|
    assert msg["result"] == {"voices": ["James Earl Jones", "Fran Drescher"]}
 |