From 405c2f96fd68b38a4c59566856b28a29e4eaa271 Mon Sep 17 00:00:00 2001 From: Andrew Jackson Date: Tue, 25 Nov 2025 17:13:15 +0000 Subject: [PATCH] Add bronze quality scale to transmission (#156388) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../components/transmission/config_flow.py | 2 +- .../components/transmission/manifest.json | 1 + .../transmission/quality_scale.yaml | 75 +++++++++++ script/hassfest/quality_scale.py | 2 - .../transmission/test_config_flow.py | 126 +++++++++--------- 5 files changed, 140 insertions(+), 66 deletions(-) create mode 100644 homeassistant/components/transmission/quality_scale.yaml diff --git a/homeassistant/components/transmission/config_flow.py b/homeassistant/components/transmission/config_flow.py index 30e9f5a146b..53c06788e91 100644 --- a/homeassistant/components/transmission/config_flow.py +++ b/homeassistant/components/transmission/config_flow.py @@ -52,7 +52,7 @@ DATA_SCHEMA = vol.Schema( class TransmissionFlowHandler(ConfigFlow, domain=DOMAIN): - """Handle Tansmission config flow.""" + """Handle Transmission config flow.""" VERSION = 1 MINOR_VERSION = 2 diff --git a/homeassistant/components/transmission/manifest.json b/homeassistant/components/transmission/manifest.json index 24c4fe48a49..69ed258f511 100644 --- a/homeassistant/components/transmission/manifest.json +++ b/homeassistant/components/transmission/manifest.json @@ -7,5 +7,6 @@ "integration_type": "service", "iot_class": "local_polling", "loggers": ["transmissionrpc"], + "quality_scale": "bronze", "requirements": ["transmission-rpc==7.0.3"] } diff --git a/homeassistant/components/transmission/quality_scale.yaml b/homeassistant/components/transmission/quality_scale.yaml new file mode 100644 index 00000000000..65b9a0c9124 --- /dev/null +++ b/homeassistant/components/transmission/quality_scale.yaml @@ -0,0 +1,75 @@ +rules: + # Bronze + action-setup: done + appropriate-polling: done + brands: done + common-modules: done + config-flow-test-coverage: done + config-flow: done + dependency-transparency: done + docs-actions: done + docs-high-level-description: done + docs-installation-instructions: done + docs-removal-instructions: done + entity-event-setup: + status: exempt + comment: | + Entities of this integration do not explicitly subscribe to events. + entity-unique-id: done + has-entity-name: done + runtime-data: done + test-before-configure: done + test-before-setup: done + unique-config-entry: done + + # Silver + action-exceptions: done + config-entry-unloading: done + docs-configuration-parameters: todo + docs-installation-parameters: done + entity-unavailable: done + integration-owner: done + log-when-unavailable: done + parallel-updates: todo + reauthentication-flow: done + test-coverage: + status: todo + comment: | + Change to mock_setup_entry to avoid repetition when expanding tests. + + # Gold + devices: + status: todo + comment: | + Add additional device detail including link to ui. + diagnostics: todo + discovery-update-info: todo + discovery: todo + docs-data-update: todo + docs-examples: done + docs-known-limitations: todo + docs-supported-devices: todo + docs-supported-functions: todo + docs-troubleshooting: todo + docs-use-cases: todo + dynamic-devices: todo + entity-category: todo + entity-device-class: todo + entity-disabled-by-default: + status: todo + comment: | + Speed sensors change so frequently that disabling by default may be appropriate. + entity-translations: done + exception-translations: done + icon-translations: + status: todo + comment: | + Add icons for sensors & switches. + reconfiguration-flow: todo + repair-issues: todo + stale-devices: todo + + # Platinum + async-dependency: todo + inject-websession: todo + strict-typing: todo diff --git a/script/hassfest/quality_scale.py b/script/hassfest/quality_scale.py index 230384b4c1c..30867c3c714 100644 --- a/script/hassfest/quality_scale.py +++ b/script/hassfest/quality_scale.py @@ -996,7 +996,6 @@ INTEGRATIONS_WITHOUT_QUALITY_SCALE_FILE = [ "trafikverket_ferry", "trafikverket_train", "trafikverket_weatherstation", - "transmission", "transport_nsw", "travisci", "trend", @@ -2026,7 +2025,6 @@ INTEGRATIONS_WITHOUT_SCALE = [ "trafikverket_ferry", "trafikverket_train", "trafikverket_weatherstation", - "transmission", "transport_nsw", "travisci", "trend", diff --git a/tests/components/transmission/test_config_flow.py b/tests/components/transmission/test_config_flow.py index b724a91f7a1..f18325e7b0a 100644 --- a/tests/components/transmission/test_config_flow.py +++ b/tests/components/transmission/test_config_flow.py @@ -38,16 +38,16 @@ async def test_form(hass: HomeAssistant) -> None: "homeassistant.components.transmission.async_setup_entry", return_value=True, ) as mock_setup_entry: - result2 = await hass.config_entries.flow.async_configure( + result = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_CONFIG_DATA, ) await hass.async_block_till_done() - assert result2["type"] is FlowResultType.CREATE_ENTRY - assert result2["title"] == "Transmission" - assert result2["data"] == MOCK_CONFIG_DATA assert len(mock_setup_entry.mock_calls) == 1 + assert result["title"] == "Transmission" + assert result["data"] == MOCK_CONFIG_DATA + assert result["type"] is FlowResultType.CREATE_ENTRY async def test_device_already_configured( @@ -62,14 +62,14 @@ async def test_device_already_configured( ) assert result["type"] is FlowResultType.FORM - result2 = await hass.config_entries.flow.async_configure( + result = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_CONFIG_DATA, ) await hass.async_block_till_done() - assert result2["type"] is FlowResultType.ABORT - assert result2["reason"] == "already_configured" + assert result["reason"] == "already_configured" + assert result["type"] is FlowResultType.ABORT async def test_options(hass: HomeAssistant) -> None: @@ -97,9 +97,9 @@ async def test_options(hass: HomeAssistant) -> None: result["flow_id"], user_input={"limit": 20} ) - assert result["type"] is FlowResultType.CREATE_ENTRY assert result["data"]["limit"] == 20 assert result["data"]["order"] == "oldest_first" + assert result["type"] is FlowResultType.CREATE_ENTRY async def test_error_on_wrong_credentials( @@ -111,48 +111,56 @@ async def test_error_on_wrong_credentials( ) mock_api.side_effect = TransmissionAuthError() - result2 = await hass.config_entries.flow.async_configure( + result = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_CONFIG_DATA, ) - assert result2["type"] is FlowResultType.FORM - assert result2["errors"] == { + assert result["type"] is FlowResultType.FORM + assert result["errors"] == { "username": "invalid_auth", "password": "invalid_auth", } - -async def test_unexpected_error(hass: HomeAssistant, mock_api: MagicMock) -> None: - """Test we handle unexpected error.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - mock_api.side_effect = TransmissionError() - result2 = await hass.config_entries.flow.async_configure( + mock_api.side_effect = None + result = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_CONFIG_DATA, ) - assert result2["type"] is FlowResultType.FORM - assert result2["errors"] == {"base": "cannot_connect"} + assert result["type"] is FlowResultType.CREATE_ENTRY -async def test_error_on_connection_failure( - hass: HomeAssistant, mock_api: MagicMock +@pytest.mark.parametrize( + ("exception", "error"), + [ + (TransmissionError, "cannot_connect"), + (TransmissionConnectError, "invalid_auth"), + ], +) +async def test_flow_errors( + hass: HomeAssistant, + mock_api: MagicMock, + exception: Exception, + error: str, ) -> None: - """Test we handle cannot connect error.""" + """Test flow errors.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - mock_api.side_effect = TransmissionConnectError() - result2 = await hass.config_entries.flow.async_configure( + mock_api.side_effect = exception + result = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_CONFIG_DATA, ) + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {"base": "cannot_connect"} - assert result2["type"] is FlowResultType.FORM - assert result2["errors"] == {"base": "cannot_connect"} + mock_api.side_effect = None + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + MOCK_CONFIG_DATA, + ) + assert result["type"] is FlowResultType.CREATE_ENTRY async def test_reauth_success(hass: HomeAssistant) -> None: @@ -173,20 +181,34 @@ async def test_reauth_success(hass: HomeAssistant) -> None: "homeassistant.components.transmission.async_setup_entry", return_value=True, ) as mock_setup_entry: - result2 = await hass.config_entries.flow.async_configure( + result = await hass.config_entries.flow.async_configure( result["flow_id"], { "password": "test-password", }, ) - assert result2["type"] is FlowResultType.ABORT - assert result2["reason"] == "reauth_successful" assert len(mock_setup_entry.mock_calls) == 1 + assert result["reason"] == "reauth_successful" + assert result["type"] is FlowResultType.ABORT -async def test_reauth_failed(hass: HomeAssistant, mock_api: MagicMock) -> None: - """Test we can't reauth due to invalid password.""" +@pytest.mark.parametrize( + ("exception", "field", "error"), + [ + (TransmissionError, "base", "cannot_connect"), + (TransmissionConnectError, "base", "cannot_connect"), + (TransmissionAuthError, "password", "invalid_auth"), + ], +) +async def test_reauth_flow_errors( + hass: HomeAssistant, + mock_api: MagicMock, + exception: Exception, + field: str, + error: str, +) -> None: + """Test flow errors.""" entry = MockConfigEntry( domain=transmission.DOMAIN, data=MOCK_CONFIG_DATA, @@ -202,44 +224,22 @@ async def test_reauth_failed(hass: HomeAssistant, mock_api: MagicMock) -> None: "name": "Mock Title", } - mock_api.side_effect = TransmissionAuthError() - result2 = await hass.config_entries.flow.async_configure( + mock_api.side_effect = exception + result = await hass.config_entries.flow.async_configure( result["flow_id"], { "password": "wrong-password", }, ) - assert result2["type"] is FlowResultType.FORM - assert result2["errors"] == {"password": "invalid_auth"} - - -async def test_reauth_failed_connection_error( - hass: HomeAssistant, mock_api: MagicMock -) -> None: - """Test we can't reauth due to connection error.""" - entry = MockConfigEntry( - domain=transmission.DOMAIN, - data=MOCK_CONFIG_DATA, - ) - entry.add_to_hass(hass) - - result = await entry.start_reauth_flow(hass) - assert result["type"] is FlowResultType.FORM - assert result["step_id"] == "reauth_confirm" - assert result["description_placeholders"] == { - "username": "user", - "name": "Mock Title", - } + assert result["errors"] == {field: error} - mock_api.side_effect = TransmissionConnectError() - result2 = await hass.config_entries.flow.async_configure( + mock_api.side_effect = None + result = await hass.config_entries.flow.async_configure( result["flow_id"], { - "password": "test-password", + "password": "correct-password", }, ) - - assert result2["type"] is FlowResultType.FORM - assert result2["errors"] == {"base": "cannot_connect"} + assert result["type"] is FlowResultType.ABORT