1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-08 17:49:37 +01:00

Version checking of Transmission (#168429)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Andrew Jackson
2026-04-21 22:26:14 +01:00
committed by GitHub
parent f2105c07de
commit f40b269752
7 changed files with 160 additions and 6 deletions
@@ -25,7 +25,11 @@ from homeassistant.const import (
Platform,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryError,
ConfigEntryNotReady,
)
from homeassistant.helpers import (
config_validation as cv,
device_registry as dr,
@@ -34,8 +38,9 @@ from homeassistant.helpers import (
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.typing import ConfigType
from .const import DEFAULT_PATH, DEFAULT_SSL, DOMAIN
from .const import DEFAULT_PATH, DEFAULT_SSL, DOMAIN, MIN_REQUIRED_TRANSMISSION_VERSION
from .coordinator import TransmissionConfigEntry, TransmissionDataUpdateCoordinator
from .helpers import create_version
from .services import async_setup_services
_LOGGER = logging.getLogger(__name__)
@@ -97,6 +102,17 @@ async def async_setup_entry(
except (TransmissionConnectError, TransmissionError) as err:
raise ConfigEntryNotReady from err
version = create_version(api.server_version)
if version.valid and version < MIN_REQUIRED_TRANSMISSION_VERSION:
raise ConfigEntryError(
translation_domain=DOMAIN,
translation_key="version_error",
translation_placeholders={
"transmission_version": api.server_version,
"min_version": MIN_REQUIRED_TRANSMISSION_VERSION,
},
)
protocol: Final = "https" if config_entry.data[CONF_SSL] else "http"
device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
@@ -40,8 +40,10 @@ from .const import (
DEFAULT_PORT,
DEFAULT_SSL,
DOMAIN,
MIN_REQUIRED_TRANSMISSION_VERSION,
SUPPORTED_ORDER_MODES,
)
from .helpers import create_version
DATA_SCHEMA = vol.Schema(
{
@@ -80,13 +82,17 @@ class TransmissionFlowHandler(ConfigFlow, domain=DOMAIN):
{CONF_HOST: user_input[CONF_HOST], CONF_PORT: user_input[CONF_PORT]}
)
try:
await get_api(self.hass, user_input)
api = await get_api(self.hass, user_input)
except TransmissionAuthError:
errors[CONF_USERNAME] = "invalid_auth"
errors[CONF_PASSWORD] = "invalid_auth"
except TransmissionConnectError, TransmissionError:
errors["base"] = "cannot_connect"
else:
version = create_version(api.server_version)
if version.valid and version < MIN_REQUIRED_TRANSMISSION_VERSION:
errors["base"] = "transmission_version"
if not errors:
return self.async_create_entry(
@@ -115,14 +121,20 @@ class TransmissionFlowHandler(ConfigFlow, domain=DOMAIN):
if user_input is not None:
user_input = {**reauth_entry.data, **user_input}
try:
await get_api(self.hass, user_input)
api = await get_api(self.hass, user_input)
except TransmissionAuthError:
errors[CONF_PASSWORD] = "invalid_auth"
except TransmissionConnectError, TransmissionError:
errors["base"] = "cannot_connect"
else:
return self.async_update_reload_and_abort(reauth_entry, data=user_input)
version = create_version(api.server_version)
if version.valid and version < MIN_REQUIRED_TRANSMISSION_VERSION:
errors["base"] = "transmission_version"
else:
return self.async_update_reload_and_abort(
reauth_entry, data=user_input
)
return self.async_show_form(
description_placeholders={
@@ -4,10 +4,13 @@ from __future__ import annotations
from collections.abc import Callable
from awesomeversion import AwesomeVersion
from transmission_rpc import Torrent
DOMAIN = "transmission"
MIN_REQUIRED_TRANSMISSION_VERSION = AwesomeVersion("4.0.0")
ORDER_NEWEST_FIRST = "newest_first"
ORDER_OLDEST_FIRST = "oldest_first"
ORDER_BEST_RATIO_FIRST = "best_ratio_first"
@@ -2,6 +2,7 @@
from typing import Any
from awesomeversion import AwesomeVersion
from transmission_rpc.torrent import Torrent
@@ -43,3 +44,8 @@ def format_torrents(
value[torrent.name] = format_torrent(torrent)
return value
def create_version(version: str) -> AwesomeVersion:
"""Convert versions, transmission has x.x.x (build)."""
return AwesomeVersion(version.split(" ", 1)[0])
@@ -6,7 +6,8 @@
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"transmission_version": "Minimum required version is 4.0.0. Please upgrade Transmission and then retry."
},
"step": {
"reauth_confirm": {
@@ -122,6 +123,9 @@
"exceptions": {
"could_not_add_torrent": {
"message": "Could not add torrent: unsupported type or no permission."
},
"version_error": {
"message": "You are running {transmission_version} of Transmission. Minimum required version is {min_version}. Please upgrade Transmission and then restart Home Assistant."
}
},
"options": {
@@ -160,6 +160,47 @@ async def test_flow_errors(
assert result["type"] is FlowResultType.CREATE_ENTRY
@pytest.mark.parametrize(
("version"),
[
("v1.0.0-RC2"),
("v0.1.0"),
("v1.9.0"),
("3.0.0"),
("3.0.0 (123798)"),
],
)
async def test_flow_version_error(
hass: HomeAssistant,
mock_transmission_client: AsyncMock,
mock_setup_entry: AsyncMock,
version: str,
) -> None:
"""Test flow version error."""
mock_transmission_client.return_value.server_version = version
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_USER},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], MOCK_CONFIG_DATA
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": "transmission_version"}
mock_transmission_client.return_value.server_version = "4.0.5 (a6fe2a64aa)"
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,
mock_transmission_client: AsyncMock,
@@ -244,3 +285,49 @@ async def test_reauth_flow_errors(
},
)
assert result["type"] is FlowResultType.ABORT
@pytest.mark.parametrize(
("version"),
[
("v1.0.0-RC2"),
("v0.1.0"),
("v1.9.0"),
("3.0.0"),
("3.0.0 (123798)"),
],
)
async def test_reauth_version_error(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_transmission_client: AsyncMock,
version: str,
) -> None:
"""Test reauth version error."""
mock_transmission_client.return_value.server_version = version
mock_config_entry.add_to_hass(hass)
result = await mock_config_entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"password": "test-password",
},
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": "transmission_version"}
mock_transmission_client.return_value.server_version = "4.0.5 (a6fe2a64aa)"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"password": "test-password",
},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
@@ -80,6 +80,32 @@ async def test_setup_failed_auth_error(
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
@pytest.mark.parametrize(
("version"),
[
("v1.0.0-RC2"),
("v0.1.0"),
("v1.9.0"),
("3.0.0"),
("3.0.0 (123798)"),
],
)
async def test_setup_failed_too_old(
hass: HomeAssistant,
mock_transmission_client: AsyncMock,
mock_config_entry: MockConfigEntry,
version: str,
) -> None:
"""Test setup of Transmission entry with too old version of Transmission."""
mock_config_entry.add_to_hass(hass)
mock_transmission_client.return_value.server_version = version
await hass.config_entries.async_setup(mock_config_entry.entry_id)
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
async def test_setup_failed_unexpected_error(
hass: HomeAssistant,
mock_transmission_client: AsyncMock,