From f41494b7cccf96648832b5f767d82fb58589500f Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:39:10 +0200 Subject: [PATCH] Ensure config_flow abort reasons have translations (#128140) * Ensure config_flow abort reasons have translations * Ignore fake_integration in application_credentials * Mark gardena_bluetooth as needs fixing * Mark google as needs fixing * Mark google_assistant_sdk as needs fixing * Mark homewizard as needs fixing * Mark homeworks as needs fixing * Mark honeywell as needs fixing * Mark jewish_calendar as needs fixing * Mark lg_netcast as needs fixing * Mark lifx as needs fixing * Mark lyric as needs fixing * Mark madvr as needs fixing * Mark matter as needs fixing * Mark melcloud as needs fixing * Mark motioneye as needs fixing * Mark ollama as needs fixing * Mark philips_js as needs fixing * Mark spotify as needs fixing * Mark srp_energy as needs fixing * Mark subaru as needs fixing * Mark tplink as needs fixing * Mark yolink as needs fixing * Mark youtube as needs fixing * Fix incorrect comment --- .../application_credentials/test_init.py | 12 +++ tests/components/conftest.py | 92 +++++++++++++++++++ .../snapshots/test_config_flow.ambr | 6 +- .../gardena_bluetooth/test_config_flow.py | 4 + tests/components/google/test_config_flow.py | 4 + .../google_assistant_sdk/test_config_flow.py | 4 + .../components/homewizard/test_config_flow.py | 4 + .../components/homeworks/test_config_flow.py | 8 ++ .../components/honeywell/test_config_flow.py | 4 + .../jewish_calendar/test_config_flow.py | 4 + .../components/lg_netcast/test_config_flow.py | 6 ++ tests/components/lifx/test_config_flow.py | 4 + tests/components/lyric/test_config_flow.py | 4 + tests/components/madvr/test_config_flow.py | 4 + tests/components/matter/test_config_flow.py | 4 + tests/components/melcloud/test_config_flow.py | 13 +++ .../components/motioneye/test_config_flow.py | 5 + tests/components/ollama/test_config_flow.py | 4 + .../components/philips_js/test_config_flow.py | 8 ++ tests/components/spotify/test_config_flow.py | 4 + .../components/srp_energy/test_config_flow.py | 4 + tests/components/subaru/test_config_flow.py | 4 + tests/components/tplink/test_config_flow.py | 4 + tests/components/yolink/test_config_flow.py | 4 + tests/components/youtube/test_config_flow.py | 4 + 25 files changed, 215 insertions(+), 3 deletions(-) diff --git a/tests/components/application_credentials/test_init.py b/tests/components/application_credentials/test_init.py index d90084fa7c9b1..686cf378fd456 100644 --- a/tests/components/application_credentials/test_init.py +++ b/tests/components/application_credentials/test_init.py @@ -48,6 +48,18 @@ TEST_DOMAIN = "fake_integration" +@pytest.fixture +def ignore_translations() -> list[str]: + """Ignore specific translations. + + We can ignore translations for the fake_integration we are testing with. + """ + return [ + f"component.{TEST_DOMAIN}.config.abort.missing_configuration", + f"component.{TEST_DOMAIN}.config.abort.missing_credentials", + ] + + @pytest.fixture async def authorization_server() -> AuthorizationServer: """Fixture AuthorizationServer for mock application_credentials integration.""" diff --git a/tests/components/conftest.py b/tests/components/conftest.py index 869f54019c9be..79f4d8b1a6994 100644 --- a/tests/components/conftest.py +++ b/tests/components/conftest.py @@ -11,8 +11,16 @@ from aiohasupervisor.models import Repository, StoreAddon, StoreInfo import pytest +from homeassistant.config_entries import ( + DISCOVERY_SOURCES, + SOURCE_SYSTEM, + ConfigEntriesFlowManager, + FlowResult, +) from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowHandler, FlowManager, FlowResultType +from homeassistant.helpers.translation import async_get_translations if TYPE_CHECKING: from homeassistant.components.hassio import AddonManager @@ -456,3 +464,87 @@ def supervisor_client() -> Generator[AsyncMock]: ), ): yield supervisor_client + + +async def _ensure_translation_exists( + hass: HomeAssistant, + ignore_translations: list[str], + category: str, + component: str, + key: str, +) -> None: + """Raise if translation doesn't exist.""" + full_key = f"component.{component}.{category}.{key}" + if full_key in ignore_translations: + return + + translations = await async_get_translations(hass, "en", category, [component]) + if full_key in translations: + return + + key_parts = key.split(".") + # Ignore step data translations if title or description exists + if ( + len(key_parts) >= 3 + and key_parts[0] == "step" + and key_parts[2] == "data" + and ( + f"component.{component}.{category}.{key_parts[0]}.{key_parts[1]}.description" + in translations + or f"component.{component}.{category}.{key_parts[0]}.{key_parts[1]}.title" + in translations + ) + ): + return + + raise ValueError( + f"Translation not found for {component}: `{category}.{key}`. " + f"Please add to homeassistant/components/{component}/strings.json" + ) + + +@pytest.fixture +def ignore_translations() -> list[str]: + """Ignore specific translations. + + Override or parametrize this fixture with a fixture that returns, + a list of translation that should be ignored. + """ + return [] + + +@pytest.fixture(autouse=True) +def check_config_translations(ignore_translations: list[str]) -> Generator[None]: + """Ensure config_flow translations are available.""" + _original = FlowManager._async_handle_step + + async def _async_handle_step( + self: FlowManager, flow: FlowHandler, *args + ) -> FlowResult: + result = await _original(self, flow, *args) + if isinstance(self, ConfigEntriesFlowManager): + category = "config" + component = flow.handler + else: + return result + + if ( + result["type"] is FlowResultType.ABORT + and flow.source != SOURCE_SYSTEM + and flow.source not in DISCOVERY_SOURCES + ): + await _ensure_translation_exists( + flow.hass, + ignore_translations, + category, + component, + f"abort.{result["reason"]}", + ) + + return result + + with patch( + "homeassistant.data_entry_flow.FlowManager._async_handle_step", + _async_handle_step, + ): + yield diff --git a/tests/components/gardena_bluetooth/snapshots/test_config_flow.ambr b/tests/components/gardena_bluetooth/snapshots/test_config_flow.ambr index 42ae66addf0c7..f28e9304baa91 100644 --- a/tests/components/gardena_bluetooth/snapshots/test_config_flow.ambr +++ b/tests/components/gardena_bluetooth/snapshots/test_config_flow.ambr @@ -138,7 +138,7 @@ 'version': 1, }) # --- -# name: test_failed_connect +# name: test_failed_connect[component.gardena_bluetooth.config.abort.cannot_connect] FlowResultSnapshot({ 'data_schema': list([ dict({ @@ -163,7 +163,7 @@ 'type': , }) # --- -# name: test_failed_connect.1 +# name: test_failed_connect[component.gardena_bluetooth.config.abort.cannot_connect].1 FlowResultSnapshot({ 'data_schema': None, 'description_placeholders': dict({ @@ -178,7 +178,7 @@ 'type': , }) # --- -# name: test_failed_connect.2 +# name: test_failed_connect[component.gardena_bluetooth.config.abort.cannot_connect].2 FlowResultSnapshot({ 'description_placeholders': dict({ 'error': 'something went wrong', diff --git a/tests/components/gardena_bluetooth/test_config_flow.py b/tests/components/gardena_bluetooth/test_config_flow.py index 3b4e9c242b385..41b880fd28e0a 100644 --- a/tests/components/gardena_bluetooth/test_config_flow.py +++ b/tests/components/gardena_bluetooth/test_config_flow.py @@ -50,6 +50,10 @@ async def test_user_selection( assert result == snapshot +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.gardena_bluetooth.config.abort.cannot_connect"], +) async def test_failed_connect( hass: HomeAssistant, mock_client: Mock, diff --git a/tests/components/google/test_config_flow.py b/tests/components/google/test_config_flow.py index b7962921ffd5e..b58c48a398eab 100644 --- a/tests/components/google/test_config_flow.py +++ b/tests/components/google/test_config_flow.py @@ -437,6 +437,10 @@ async def test_multiple_config_entries( assert len(entries) == 2 +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.google.config.abort.missing_credentials"], +) async def test_missing_configuration( hass: HomeAssistant, ) -> None: diff --git a/tests/components/google_assistant_sdk/test_config_flow.py b/tests/components/google_assistant_sdk/test_config_flow.py index d66d12509e89d..b6ee701b228ee 100644 --- a/tests/components/google_assistant_sdk/test_config_flow.py +++ b/tests/components/google_assistant_sdk/test_config_flow.py @@ -157,6 +157,10 @@ async def test_reauth( assert config_entry.data["token"].get("refresh_token") == "mock-refresh-token" +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.google_assistant_sdk.config.abort.single_instance_allowed"], +) @pytest.mark.usefixtures("current_request_with_host") async def test_single_instance_allowed( hass: HomeAssistant, diff --git a/tests/components/homewizard/test_config_flow.py b/tests/components/homewizard/test_config_flow.py index 442659f2aad2d..6605eb592cfe6 100644 --- a/tests/components/homewizard/test_config_flow.py +++ b/tests/components/homewizard/test_config_flow.py @@ -302,6 +302,10 @@ async def test_error_flow( assert result["type"] is FlowResultType.CREATE_ENTRY +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.homewizard.config.abort.unsupported_api_version"], +) @pytest.mark.parametrize( ("exception", "reason"), [ diff --git a/tests/components/homeworks/test_config_flow.py b/tests/components/homeworks/test_config_flow.py index e8c4ab15b3def..cca09c10e70de 100644 --- a/tests/components/homeworks/test_config_flow.py +++ b/tests/components/homeworks/test_config_flow.py @@ -235,6 +235,10 @@ async def test_user_flow_cannot_connect( assert result["step_id"] == "user" +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.homeworks.config.abort.reconfigure_successful"], +) async def test_reconfigure_flow( hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_homeworks: MagicMock ) -> None: @@ -322,6 +326,10 @@ async def test_reconfigure_flow_flow_duplicate( assert result["errors"] == {"base": "duplicated_host_port"} +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.homeworks.config.abort.reconfigure_successful"], +) async def test_reconfigure_flow_flow_no_change( hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_homeworks: MagicMock ) -> None: diff --git a/tests/components/honeywell/test_config_flow.py b/tests/components/honeywell/test_config_flow.py index ed9c86f5e1038..b1c0b28f5372f 100644 --- a/tests/components/honeywell/test_config_flow.py +++ b/tests/components/honeywell/test_config_flow.py @@ -120,6 +120,10 @@ async def test_create_option_entry( } +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.honeywell.config.abort.reauth_successful"], +) async def test_reauth_flow(hass: HomeAssistant) -> None: """Test a successful reauth flow.""" diff --git a/tests/components/jewish_calendar/test_config_flow.py b/tests/components/jewish_calendar/test_config_flow.py index fe31e7b6002e4..23b0e9898f3bf 100644 --- a/tests/components/jewish_calendar/test_config_flow.py +++ b/tests/components/jewish_calendar/test_config_flow.py @@ -166,6 +166,10 @@ async def test_options_reconfigure( ) +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.jewish_calendar.config.abort.reconfigure_successful"], +) async def test_reconfigure( hass: HomeAssistant, mock_config_entry: MockConfigEntry ) -> None: diff --git a/tests/components/lg_netcast/test_config_flow.py b/tests/components/lg_netcast/test_config_flow.py index 0270758248403..7959c0c445ec8 100644 --- a/tests/components/lg_netcast/test_config_flow.py +++ b/tests/components/lg_netcast/test_config_flow.py @@ -3,6 +3,8 @@ from datetime import timedelta from unittest.mock import DEFAULT, patch +import pytest + from homeassistant import data_entry_flow from homeassistant.components.lg_netcast.const import DOMAIN from homeassistant.config_entries import SOURCE_USER @@ -112,6 +114,10 @@ async def test_manual_host_unsuccessful_details_response(hass: HomeAssistant) -> assert result["reason"] == "cannot_connect" +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.lg_netcast.config.abort.invalid_host"], +) async def test_manual_host_no_unique_id_response(hass: HomeAssistant) -> None: """Test manual host configuration.""" with _patch_lg_netcast(no_unique_id=True): diff --git a/tests/components/lifx/test_config_flow.py b/tests/components/lifx/test_config_flow.py index d1a6920f84af5..a37a4b412d8f9 100644 --- a/tests/components/lifx/test_config_flow.py +++ b/tests/components/lifx/test_config_flow.py @@ -101,6 +101,10 @@ async def test_discovery(hass: HomeAssistant) -> None: assert result2["reason"] == "no_devices_found" +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.lifx.config.abort.cannot_connect"], +) async def test_discovery_but_cannot_connect(hass: HomeAssistant) -> None: """Test we can discover the device but we cannot connect.""" with _patch_discovery(), _patch_config_flow_try_connect(no_device=True): diff --git a/tests/components/lyric/test_config_flow.py b/tests/components/lyric/test_config_flow.py index e1916924e9f7d..7ddafccf704db 100644 --- a/tests/components/lyric/test_config_flow.py +++ b/tests/components/lyric/test_config_flow.py @@ -36,6 +36,10 @@ async def mock_impl(hass: HomeAssistant) -> None: ) +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.lyric.config.abort.missing_credentials"], +) async def test_abort_if_no_configuration(hass: HomeAssistant) -> None: """Check flow abort when no configuration.""" result = await hass.config_entries.flow.async_init( diff --git a/tests/components/madvr/test_config_flow.py b/tests/components/madvr/test_config_flow.py index 7b31ec6c17c5d..35db8a01b5bf9 100644 --- a/tests/components/madvr/test_config_flow.py +++ b/tests/components/madvr/test_config_flow.py @@ -165,6 +165,10 @@ async def test_reconfigure_flow( mock_madvr_client.async_cancel_tasks.assert_called() +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.madvr.config.abort.set_up_new_device"], +) async def test_reconfigure_new_device( hass: HomeAssistant, mock_madvr_client: AsyncMock, diff --git a/tests/components/matter/test_config_flow.py b/tests/components/matter/test_config_flow.py index de964d482851e..da773a360b8c2 100644 --- a/tests/components/matter/test_config_flow.py +++ b/tests/components/matter/test_config_flow.py @@ -827,6 +827,10 @@ async def test_addon_running( assert setup_entry.call_count == 1 +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.matter.config.abort.cannot_connect"], +) @pytest.mark.parametrize( ( "discovery_info", diff --git a/tests/components/melcloud/test_config_flow.py b/tests/components/melcloud/test_config_flow.py index 3f6e42ac26498..b575d5073dcba 100644 --- a/tests/components/melcloud/test_config_flow.py +++ b/tests/components/melcloud/test_config_flow.py @@ -73,6 +73,10 @@ async def test_form(hass: HomeAssistant, mock_login, mock_get_devices) -> None: assert len(mock_setup_entry.mock_calls) == 1 +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.melcloud.config.abort.cannot_connect"], +) @pytest.mark.parametrize( ("error", "reason"), [(ClientError(), "cannot_connect"), (TimeoutError(), "cannot_connect")], @@ -94,6 +98,15 @@ async def test_form_errors( assert result["reason"] == reason +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + [ + [ + "component.melcloud.config.abort.cannot_connect", + "component.melcloud.config.abort.invalid_auth", + ], + ], +) @pytest.mark.parametrize( ("error", "message"), [ diff --git a/tests/components/motioneye/test_config_flow.py b/tests/components/motioneye/test_config_flow.py index d2ec91b08e38f..c15d0ade03507 100644 --- a/tests/components/motioneye/test_config_flow.py +++ b/tests/components/motioneye/test_config_flow.py @@ -7,6 +7,7 @@ MotionEyeClientInvalidAuthError, MotionEyeClientRequestError, ) +import pytest from homeassistant import config_entries from homeassistant.components.hassio import HassioServiceInfo @@ -390,6 +391,10 @@ async def test_hassio_ignored(hass: HomeAssistant) -> None: assert result.get("reason") == "already_configured" +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.motioneye.config.abort.already_in_progress"], +) async def test_hassio_abort_if_already_in_progress(hass: HomeAssistant) -> None: """Test Supervisor discovered flow aborts if user flow in progress.""" result = await hass.config_entries.flow.async_init( diff --git a/tests/components/ollama/test_config_flow.py b/tests/components/ollama/test_config_flow.py index 7755f2208b43d..82c954a1737c5 100644 --- a/tests/components/ollama/test_config_flow.py +++ b/tests/components/ollama/test_config_flow.py @@ -204,6 +204,10 @@ async def test_form_errors(hass: HomeAssistant, side_effect, error) -> None: assert result2["errors"] == {"base": error} +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.ollama.config.abort.download_failed"], +) async def test_download_error(hass: HomeAssistant) -> None: """Test we handle errors while downloading a model.""" result = await hass.config_entries.flow.async_init( diff --git a/tests/components/philips_js/test_config_flow.py b/tests/components/philips_js/test_config_flow.py index 80d059618133d..c08885634dbfe 100644 --- a/tests/components/philips_js/test_config_flow.py +++ b/tests/components/philips_js/test_config_flow.py @@ -161,6 +161,10 @@ async def test_pairing(hass: HomeAssistant, mock_tv_pairable, mock_setup_entry) assert len(mock_setup_entry.mock_calls) == 1 +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.philips_js.config.abort.pairing_failure"], +) async def test_pair_request_failed( hass: HomeAssistant, mock_tv_pairable, mock_setup_entry ) -> None: @@ -188,6 +192,10 @@ async def test_pair_request_failed( } +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.philips_js.config.abort.pairing_failure"], +) async def test_pair_grant_failed( hass: HomeAssistant, mock_tv_pairable, mock_setup_entry ) -> None: diff --git a/tests/components/spotify/test_config_flow.py b/tests/components/spotify/test_config_flow.py index f4719c0147cf8..668f6bf1a451a 100644 --- a/tests/components/spotify/test_config_flow.py +++ b/tests/components/spotify/test_config_flow.py @@ -29,6 +29,10 @@ ) +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.spotify.config.abort.missing_credentials"], +) async def test_abort_if_no_configuration(hass: HomeAssistant) -> None: """Check flow aborts when no configuration is present.""" result = await hass.config_entries.flow.async_init( diff --git a/tests/components/srp_energy/test_config_flow.py b/tests/components/srp_energy/test_config_flow.py index e3abb3c98df2d..149e08014ac3b 100644 --- a/tests/components/srp_energy/test_config_flow.py +++ b/tests/components/srp_energy/test_config_flow.py @@ -100,6 +100,10 @@ async def test_form_invalid_auth( assert result["errors"] == {"base": "invalid_auth"} +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.srp_energy.config.abort.unknown"], +) async def test_form_unknown_error( hass: HomeAssistant, mock_srp_energy_config_flow: MagicMock, diff --git a/tests/components/subaru/test_config_flow.py b/tests/components/subaru/test_config_flow.py index 6abc544c92a4e..d930aafbdfb1e 100644 --- a/tests/components/subaru/test_config_flow.py +++ b/tests/components/subaru/test_config_flow.py @@ -192,6 +192,10 @@ async def test_two_factor_request_success( assert len(mock_two_factor_request.mock_calls) == 1 +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.subaru.config.abort.two_factor_request_failed"], +) async def test_two_factor_request_fail( hass: HomeAssistant, two_factor_start_form ) -> None: diff --git a/tests/components/tplink/test_config_flow.py b/tests/components/tplink/test_config_flow.py index 40bd4383513eb..e8778b59225b8 100644 --- a/tests/components/tplink/test_config_flow.py +++ b/tests/components/tplink/test_config_flow.py @@ -1348,6 +1348,10 @@ async def test_reauth_errors( assert result3["reason"] == "reauth_successful" +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.tplink.config.abort.cannot_connect"], +) @pytest.mark.parametrize( ("error_type", "expected_flow"), [ diff --git a/tests/components/yolink/test_config_flow.py b/tests/components/yolink/test_config_flow.py index 1dd71368d7375..f981ed69bbee0 100644 --- a/tests/components/yolink/test_config_flow.py +++ b/tests/components/yolink/test_config_flow.py @@ -22,6 +22,10 @@ DOMAIN = "yolink" +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.yolink.config.abort.missing_credentials"], +) async def test_abort_if_no_configuration(hass: HomeAssistant) -> None: """Check flow abort when no configuration.""" result = await hass.config_entries.flow.async_init( diff --git a/tests/components/youtube/test_config_flow.py b/tests/components/youtube/test_config_flow.py index 73652d9b2391e..dc312e8c5efaf 100644 --- a/tests/components/youtube/test_config_flow.py +++ b/tests/components/youtube/test_config_flow.py @@ -210,6 +210,10 @@ async def test_flow_http_error( ) +@pytest.mark.parametrize( # Remove when translations fixed + "ignore_translations", + ["component.youtube.config.abort.wrong_account"], +) @pytest.mark.parametrize( ("fixture", "abort_reason", "placeholders", "call_count", "access_token"), [