mirror of
https://github.com/home-assistant/core.git
synced 2025-12-24 21:06:19 +00:00
Deprecate cloud tts gender (#112256)
* Deprecate cloud tts gender option * Update http api and prefs * Test migration of prefs to minor version 4 * Adjust breaking date * Add test for bad voice in http api * Flatten tts info * Fix comments * Fix comment date Co-authored-by: Erik Montnemery <erik@montnemery.com> * Clarify voice validator --------- Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
@@ -6,7 +6,7 @@ from http import HTTPStatus
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from hass_nabucasa.voice import MAP_VOICE, VoiceError, VoiceTokenError
|
||||
from hass_nabucasa.voice import TTS_VOICES, VoiceError, VoiceTokenError
|
||||
import pytest
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -44,7 +44,11 @@ async def internal_url_mock(hass: HomeAssistant) -> None:
|
||||
|
||||
def test_default_exists() -> None:
|
||||
"""Test our default language exists."""
|
||||
assert const.DEFAULT_TTS_DEFAULT_VOICE in MAP_VOICE
|
||||
assert const.DEFAULT_TTS_DEFAULT_VOICE[0] in TTS_VOICES
|
||||
assert (
|
||||
const.DEFAULT_TTS_DEFAULT_VOICE[1]
|
||||
in TTS_VOICES[const.DEFAULT_TTS_DEFAULT_VOICE[0]]
|
||||
)
|
||||
|
||||
|
||||
def test_schema() -> None:
|
||||
@@ -105,7 +109,7 @@ async def test_prefs_default_voice(
|
||||
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert cloud.client.prefs.tts_default_voice == ("en-US", "female")
|
||||
assert cloud.client.prefs.tts_default_voice == ("en-US", "JennyNeural")
|
||||
|
||||
on_start_callback = cloud.register_on_start.call_args[0][0]
|
||||
await on_start_callback()
|
||||
@@ -116,13 +120,13 @@ async def test_prefs_default_voice(
|
||||
assert engine is not None
|
||||
# The platform config provider will be overridden by the discovery info provider.
|
||||
assert engine.default_language == "en-US"
|
||||
assert engine.default_options == {"gender": "female", "audio_output": "mp3"}
|
||||
assert engine.default_options == {"audio_output": "mp3", "voice": "JennyNeural"}
|
||||
|
||||
await set_cloud_prefs({"tts_default_voice": ("nl-NL", "male")})
|
||||
await set_cloud_prefs({"tts_default_voice": ("nl-NL", "MaartenNeural")})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert engine.default_language == "nl-NL"
|
||||
assert engine.default_options == {"gender": "male", "audio_output": "mp3"}
|
||||
assert engine.default_options == {"audio_output": "mp3", "voice": "MaartenNeural"}
|
||||
|
||||
|
||||
async def test_deprecated_platform_config(
|
||||
@@ -224,11 +228,11 @@ async def test_get_tts_audio(
|
||||
"url": (
|
||||
"http://example.local:8123/api/tts_proxy/"
|
||||
"42f18378fd4393d18c8dd11d03fa9563c1e54491"
|
||||
f"_en-us_e09b5a0968_{expected_url_suffix}.mp3"
|
||||
f"_en-us_5c97d21c48_{expected_url_suffix}.mp3"
|
||||
),
|
||||
"path": (
|
||||
"/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491"
|
||||
f"_en-us_e09b5a0968_{expected_url_suffix}.mp3"
|
||||
f"_en-us_5c97d21c48_{expected_url_suffix}.mp3"
|
||||
),
|
||||
}
|
||||
await hass.async_block_till_done()
|
||||
@@ -237,7 +241,7 @@ async def test_get_tts_audio(
|
||||
assert mock_process_tts.call_args is not None
|
||||
assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door."
|
||||
assert mock_process_tts.call_args.kwargs["language"] == "en-US"
|
||||
assert mock_process_tts.call_args.kwargs["gender"] == "female"
|
||||
assert mock_process_tts.call_args.kwargs["gender"] is None
|
||||
assert mock_process_tts.call_args.kwargs["output"] == "mp3"
|
||||
|
||||
|
||||
@@ -276,11 +280,11 @@ async def test_get_tts_audio_logged_out(
|
||||
"url": (
|
||||
"http://example.local:8123/api/tts_proxy/"
|
||||
"42f18378fd4393d18c8dd11d03fa9563c1e54491"
|
||||
f"_en-us_e09b5a0968_{expected_url_suffix}.mp3"
|
||||
f"_en-us_5c97d21c48_{expected_url_suffix}.mp3"
|
||||
),
|
||||
"path": (
|
||||
"/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491"
|
||||
f"_en-us_e09b5a0968_{expected_url_suffix}.mp3"
|
||||
f"_en-us_5c97d21c48_{expected_url_suffix}.mp3"
|
||||
),
|
||||
}
|
||||
await hass.async_block_till_done()
|
||||
@@ -289,7 +293,7 @@ async def test_get_tts_audio_logged_out(
|
||||
assert mock_process_tts.call_args is not None
|
||||
assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door."
|
||||
assert mock_process_tts.call_args.kwargs["language"] == "en-US"
|
||||
assert mock_process_tts.call_args.kwargs["gender"] == "female"
|
||||
assert mock_process_tts.call_args.kwargs["gender"] is None
|
||||
assert mock_process_tts.call_args.kwargs["output"] == "mp3"
|
||||
|
||||
|
||||
@@ -340,11 +344,11 @@ async def test_tts_entity(
|
||||
"url": (
|
||||
"http://example.local:8123/api/tts_proxy/"
|
||||
"42f18378fd4393d18c8dd11d03fa9563c1e54491"
|
||||
f"_en-us_e09b5a0968_{entity_id}.mp3"
|
||||
f"_en-us_5c97d21c48_{entity_id}.mp3"
|
||||
),
|
||||
"path": (
|
||||
"/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491"
|
||||
f"_en-us_e09b5a0968_{entity_id}.mp3"
|
||||
f"_en-us_5c97d21c48_{entity_id}.mp3"
|
||||
),
|
||||
}
|
||||
await hass.async_block_till_done()
|
||||
@@ -353,7 +357,7 @@ async def test_tts_entity(
|
||||
assert mock_process_tts.call_args is not None
|
||||
assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door."
|
||||
assert mock_process_tts.call_args.kwargs["language"] == "en-US"
|
||||
assert mock_process_tts.call_args.kwargs["gender"] == "female"
|
||||
assert mock_process_tts.call_args.kwargs["gender"] is None
|
||||
assert mock_process_tts.call_args.kwargs["output"] == "mp3"
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
@@ -480,11 +484,11 @@ async def test_deprecated_voice(
|
||||
"url": (
|
||||
"http://example.local:8123/api/tts_proxy/"
|
||||
"42f18378fd4393d18c8dd11d03fa9563c1e54491"
|
||||
f"_{language.lower()}_1c4ec2f170_{expected_url_suffix}.mp3"
|
||||
f"_{language.lower()}_87567e3e29_{expected_url_suffix}.mp3"
|
||||
),
|
||||
"path": (
|
||||
"/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491"
|
||||
f"_{language.lower()}_1c4ec2f170_{expected_url_suffix}.mp3"
|
||||
f"_{language.lower()}_87567e3e29_{expected_url_suffix}.mp3"
|
||||
),
|
||||
}
|
||||
await hass.async_block_till_done()
|
||||
@@ -493,7 +497,7 @@ async def test_deprecated_voice(
|
||||
assert mock_process_tts.call_args is not None
|
||||
assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door."
|
||||
assert mock_process_tts.call_args.kwargs["language"] == language
|
||||
assert mock_process_tts.call_args.kwargs["gender"] == "female"
|
||||
assert mock_process_tts.call_args.kwargs["gender"] is None
|
||||
assert mock_process_tts.call_args.kwargs["voice"] == replacement_voice
|
||||
assert mock_process_tts.call_args.kwargs["output"] == "mp3"
|
||||
issue = issue_registry.async_get_issue(
|
||||
@@ -513,11 +517,11 @@ async def test_deprecated_voice(
|
||||
"url": (
|
||||
"http://example.local:8123/api/tts_proxy/"
|
||||
"42f18378fd4393d18c8dd11d03fa9563c1e54491"
|
||||
f"_{language.lower()}_a1c3b0ac0e_{expected_url_suffix}.mp3"
|
||||
f"_{language.lower()}_13646b7d32_{expected_url_suffix}.mp3"
|
||||
),
|
||||
"path": (
|
||||
"/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491"
|
||||
f"_{language.lower()}_a1c3b0ac0e_{expected_url_suffix}.mp3"
|
||||
f"_{language.lower()}_13646b7d32_{expected_url_suffix}.mp3"
|
||||
),
|
||||
}
|
||||
await hass.async_block_till_done()
|
||||
@@ -526,7 +530,7 @@ async def test_deprecated_voice(
|
||||
assert mock_process_tts.call_args is not None
|
||||
assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door."
|
||||
assert mock_process_tts.call_args.kwargs["language"] == language
|
||||
assert mock_process_tts.call_args.kwargs["gender"] == "female"
|
||||
assert mock_process_tts.call_args.kwargs["gender"] is None
|
||||
assert mock_process_tts.call_args.kwargs["voice"] == replacement_voice
|
||||
assert mock_process_tts.call_args.kwargs["output"] == "mp3"
|
||||
issue = issue_registry.async_get_issue(
|
||||
@@ -542,3 +546,107 @@ async def test_deprecated_voice(
|
||||
"deprecated_voice": deprecated_voice,
|
||||
"replacement_voice": replacement_voice,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("data", "expected_url_suffix"),
|
||||
[
|
||||
({"platform": DOMAIN}, DOMAIN),
|
||||
({"engine_id": DOMAIN}, DOMAIN),
|
||||
({"engine_id": "tts.home_assistant_cloud"}, "tts.home_assistant_cloud"),
|
||||
],
|
||||
)
|
||||
async def test_deprecated_gender(
|
||||
hass: HomeAssistant,
|
||||
issue_registry: IssueRegistry,
|
||||
cloud: MagicMock,
|
||||
hass_client: ClientSessionGenerator,
|
||||
data: dict[str, Any],
|
||||
expected_url_suffix: str,
|
||||
) -> None:
|
||||
"""Test we create an issue when a deprecated gender is used for text-to-speech."""
|
||||
language = "zh-CN"
|
||||
gender_option = "male"
|
||||
mock_process_tts = AsyncMock(
|
||||
return_value=b"",
|
||||
)
|
||||
cloud.voice.process_tts = mock_process_tts
|
||||
|
||||
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
|
||||
await hass.async_block_till_done()
|
||||
await cloud.login("test-user", "test-pass")
|
||||
client = await hass_client()
|
||||
|
||||
# Test without deprecated gender option.
|
||||
url = "/api/tts_get_url"
|
||||
data |= {
|
||||
"message": "There is someone at the door.",
|
||||
"language": language,
|
||||
}
|
||||
|
||||
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"
|
||||
f"_{language.lower()}_5c97d21c48_{expected_url_suffix}.mp3"
|
||||
),
|
||||
"path": (
|
||||
"/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491"
|
||||
f"_{language.lower()}_5c97d21c48_{expected_url_suffix}.mp3"
|
||||
),
|
||||
}
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_process_tts.call_count == 1
|
||||
assert mock_process_tts.call_args is not None
|
||||
assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door."
|
||||
assert mock_process_tts.call_args.kwargs["language"] == language
|
||||
assert mock_process_tts.call_args.kwargs["voice"] == "JennyNeural"
|
||||
assert mock_process_tts.call_args.kwargs["output"] == "mp3"
|
||||
issue = issue_registry.async_get_issue("cloud", "deprecated_gender")
|
||||
assert issue is None
|
||||
mock_process_tts.reset_mock()
|
||||
|
||||
# Test with deprecated gender option.
|
||||
data["options"] = {"gender": gender_option}
|
||||
|
||||
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"
|
||||
f"_{language.lower()}_5dded72256_{expected_url_suffix}.mp3"
|
||||
),
|
||||
"path": (
|
||||
"/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491"
|
||||
f"_{language.lower()}_5dded72256_{expected_url_suffix}.mp3"
|
||||
),
|
||||
}
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_process_tts.call_count == 1
|
||||
assert mock_process_tts.call_args is not None
|
||||
assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door."
|
||||
assert mock_process_tts.call_args.kwargs["language"] == language
|
||||
assert mock_process_tts.call_args.kwargs["gender"] == gender_option
|
||||
assert mock_process_tts.call_args.kwargs["voice"] == "JennyNeural"
|
||||
assert mock_process_tts.call_args.kwargs["output"] == "mp3"
|
||||
issue = issue_registry.async_get_issue("cloud", "deprecated_gender")
|
||||
assert issue is not None
|
||||
assert issue.breaks_in_ha_version == "2024.10.0"
|
||||
assert issue.is_fixable is True
|
||||
assert issue.is_persistent is True
|
||||
assert issue.severity == IssueSeverity.WARNING
|
||||
assert issue.translation_key == "deprecated_gender"
|
||||
assert issue.translation_placeholders == {
|
||||
"integration_name": "Home Assistant Cloud",
|
||||
"deprecated_option": "gender",
|
||||
"replacement_option": "voice",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user