diff --git a/homeassistant/components/webdav/__init__.py b/homeassistant/components/webdav/__init__.py index 952a68d829f..36a03dce4d7 100644 --- a/homeassistant/components/webdav/__init__.py +++ b/homeassistant/components/webdav/__init__.py @@ -13,7 +13,11 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady from .const import CONF_BACKUP_PATH, DATA_BACKUP_AGENT_LISTENERS, DOMAIN -from .helpers import async_create_client, async_ensure_path_exists +from .helpers import ( + async_create_client, + async_ensure_path_exists, + async_migrate_wrong_folder_path, +) type WebDavConfigEntry = ConfigEntry[Client] @@ -46,10 +50,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: WebDavConfigEntry) -> bo translation_key="cannot_connect", ) + path = entry.data.get(CONF_BACKUP_PATH, "/") + await async_migrate_wrong_folder_path(client, path) + # Ensure the backup directory exists - if not await async_ensure_path_exists( - client, entry.data.get(CONF_BACKUP_PATH, "/") - ): + if not await async_ensure_path_exists(client, path): raise ConfigEntryNotReady( translation_domain=DOMAIN, translation_key="cannot_access_or_create_backup_path", diff --git a/homeassistant/components/webdav/helpers.py b/homeassistant/components/webdav/helpers.py index 9f91ed3bdb3..5db15bba0f7 100644 --- a/homeassistant/components/webdav/helpers.py +++ b/homeassistant/components/webdav/helpers.py @@ -1,10 +1,18 @@ """Helper functions for the WebDAV component.""" +import logging + from aiowebdav2.client import Client, ClientOptions +from aiowebdav2.exceptions import WebDavError from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + @callback def async_create_client( @@ -36,3 +44,24 @@ async def async_ensure_path_exists(client: Client, path: str) -> bool: return False return True + + +async def async_migrate_wrong_folder_path(client: Client, path: str) -> None: + """Migrate the wrong encoded folder path to the correct one.""" + wrong_path = path.replace(" ", "%20") + if await client.check(wrong_path): + try: + await client.move(wrong_path, path) + except WebDavError as err: + raise ConfigEntryNotReady( + translation_domain=DOMAIN, + translation_key="failed_to_migrate_folder", + translation_placeholders={ + "wrong_path": wrong_path, + "correct_path": path, + }, + ) from err + + _LOGGER.debug( + "Migrated wrong encoded folder path from %s to %s", wrong_path, path + ) diff --git a/homeassistant/components/webdav/manifest.json b/homeassistant/components/webdav/manifest.json index fd3c749781e..30028cb28c9 100644 --- a/homeassistant/components/webdav/manifest.json +++ b/homeassistant/components/webdav/manifest.json @@ -8,5 +8,5 @@ "iot_class": "cloud_polling", "loggers": ["aiowebdav2"], "quality_scale": "bronze", - "requirements": ["aiowebdav2==0.4.1"] + "requirements": ["aiowebdav2==0.4.2"] } diff --git a/homeassistant/components/webdav/strings.json b/homeassistant/components/webdav/strings.json index 57117cdd9de..b03ffaf2a3d 100644 --- a/homeassistant/components/webdav/strings.json +++ b/homeassistant/components/webdav/strings.json @@ -36,6 +36,9 @@ }, "cannot_access_or_create_backup_path": { "message": "Cannot access or create backup path. Please check the path and permissions." + }, + "failed_to_migrate_folder": { + "message": "Failed to migrate wrong encoded folder \"{wrong_path}\" to \"{correct_path}\"." } } } diff --git a/requirements_all.txt b/requirements_all.txt index e02eb2873d6..79570685c47 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -422,7 +422,7 @@ aiowaqi==3.1.0 aiowatttime==0.1.1 # homeassistant.components.webdav -aiowebdav2==0.4.1 +aiowebdav2==0.4.2 # homeassistant.components.webostv aiowebostv==0.7.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2914c8e09f7..ca183124a17 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -404,7 +404,7 @@ aiowaqi==3.1.0 aiowatttime==0.1.1 # homeassistant.components.webdav -aiowebdav2==0.4.1 +aiowebdav2==0.4.2 # homeassistant.components.webostv aiowebostv==0.7.3 diff --git a/tests/components/webdav/__init__.py b/tests/components/webdav/__init__.py index 33e0222fb34..3b901bdd308 100644 --- a/tests/components/webdav/__init__.py +++ b/tests/components/webdav/__init__.py @@ -1 +1,14 @@ """Tests for the WebDAV integration.""" + +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +async def setup_integration( + hass: HomeAssistant, mock_config_entry: MockConfigEntry +) -> None: + """Set up the WebDAV integration for testing.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() diff --git a/tests/components/webdav/conftest.py b/tests/components/webdav/conftest.py index 4fdd6fb7870..645e2111364 100644 --- a/tests/components/webdav/conftest.py +++ b/tests/components/webdav/conftest.py @@ -62,4 +62,5 @@ def mock_webdav_client() -> Generator[AsyncMock]: mock.download_iter.side_effect = _download_mock mock.upload_iter.return_value = None mock.clean.return_value = None + mock.move.return_value = None yield mock diff --git a/tests/components/webdav/test_init.py b/tests/components/webdav/test_init.py new file mode 100644 index 00000000000..c267f7c3251 --- /dev/null +++ b/tests/components/webdav/test_init.py @@ -0,0 +1,96 @@ +"""Test WebDAV component setup.""" + +from unittest.mock import AsyncMock + +from aiowebdav2.exceptions import WebDavError +import pytest + +from homeassistant.components.webdav.const import CONF_BACKUP_PATH, DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME +from homeassistant.core import HomeAssistant + +from . import setup_integration + +from tests.common import MockConfigEntry + + +async def test_migrate_wrong_path( + hass: HomeAssistant, webdav_client: AsyncMock +) -> None: + """Test migration of wrong encoded folder path.""" + webdav_client.list_with_properties.return_value = [ + {"/wrong%20path": []}, + ] + + config_entry = MockConfigEntry( + title="user@webdav.demo", + domain=DOMAIN, + data={ + CONF_URL: "https://webdav.demo", + CONF_USERNAME: "user", + CONF_PASSWORD: "supersecretpassword", + CONF_BACKUP_PATH: "/wrong path", + }, + entry_id="01JKXV07ASC62D620DGYNG2R8H", + ) + await setup_integration(hass, config_entry) + + webdav_client.move.assert_called_once_with("/wrong%20path", "/wrong path") + + +async def test_migrate_non_wrong_path( + hass: HomeAssistant, webdav_client: AsyncMock +) -> None: + """Test no migration of correct folder path.""" + webdav_client.list_with_properties.return_value = [ + {"/correct path": []}, + ] + webdav_client.check.side_effect = lambda path: path == "/correct path" + + config_entry = MockConfigEntry( + title="user@webdav.demo", + domain=DOMAIN, + data={ + CONF_URL: "https://webdav.demo", + CONF_USERNAME: "user", + CONF_PASSWORD: "supersecretpassword", + CONF_BACKUP_PATH: "/correct path", + }, + entry_id="01JKXV07ASC62D620DGYNG2R8H", + ) + + await setup_integration(hass, config_entry) + + webdav_client.move.assert_not_called() + + +async def test_migrate_error( + hass: HomeAssistant, + webdav_client: AsyncMock, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test migration of wrong encoded folder path with error.""" + webdav_client.list_with_properties.return_value = [ + {"/wrong%20path": []}, + ] + webdav_client.move.side_effect = WebDavError("Failed to move") + + config_entry = MockConfigEntry( + title="user@webdav.demo", + domain=DOMAIN, + data={ + CONF_URL: "https://webdav.demo", + CONF_USERNAME: "user", + CONF_PASSWORD: "supersecretpassword", + CONF_BACKUP_PATH: "/wrong path", + }, + entry_id="01JKXV07ASC62D620DGYNG2R8H", + ) + await setup_integration(hass, config_entry) + + assert config_entry.state is ConfigEntryState.SETUP_RETRY + assert ( + 'Failed to migrate wrong encoded folder "/wrong%20path" to "/wrong path"' + in caplog.text + )