mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Allow rename of the backup folder for OneDrive (#138407)
This commit is contained in:
parent
800fe1b01e
commit
c1e5673cbd
@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from html import unescape
|
||||
from json import dumps, loads
|
||||
import logging
|
||||
@ -10,10 +11,10 @@ from typing import cast
|
||||
from onedrive_personal_sdk import OneDriveClient
|
||||
from onedrive_personal_sdk.exceptions import (
|
||||
AuthenticationError,
|
||||
HttpRequestException,
|
||||
NotFoundError,
|
||||
OneDriveException,
|
||||
)
|
||||
from onedrive_personal_sdk.models.items import ItemUpdate
|
||||
from onedrive_personal_sdk.models.items import Item, ItemUpdate
|
||||
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN, Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
@ -25,7 +26,7 @@ from homeassistant.helpers.config_entry_oauth2_flow import (
|
||||
)
|
||||
from homeassistant.helpers.instance_id import async_get as async_get_instance_id
|
||||
|
||||
from .const import DATA_BACKUP_AGENT_LISTENERS, DOMAIN
|
||||
from .const import CONF_FOLDER_ID, CONF_FOLDER_NAME, DATA_BACKUP_AGENT_LISTENERS, DOMAIN
|
||||
from .coordinator import (
|
||||
OneDriveConfigEntry,
|
||||
OneDriveRuntimeData,
|
||||
@ -50,33 +51,38 @@ async def async_setup_entry(hass: HomeAssistant, entry: OneDriveConfigEntry) ->
|
||||
client = OneDriveClient(get_access_token, async_get_clientsession(hass))
|
||||
|
||||
# get approot, will be created automatically if it does not exist
|
||||
try:
|
||||
approot = await client.get_approot()
|
||||
except AuthenticationError as err:
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN, translation_key="authentication_failed"
|
||||
) from err
|
||||
except (HttpRequestException, OneDriveException, TimeoutError) as err:
|
||||
_LOGGER.debug("Failed to get approot", exc_info=True)
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="failed_to_get_folder",
|
||||
translation_placeholders={"folder": "approot"},
|
||||
) from err
|
||||
approot = await _handle_item_operation(client.get_approot, "approot")
|
||||
folder_name = entry.data[CONF_FOLDER_NAME]
|
||||
|
||||
instance_id = await async_get_instance_id(hass)
|
||||
backup_folder_name = f"backups_{instance_id[:8]}"
|
||||
try:
|
||||
backup_folder = await client.create_folder(
|
||||
parent_id=approot.id, name=backup_folder_name
|
||||
backup_folder = await _handle_item_operation(
|
||||
lambda: client.get_drive_item(path_or_id=entry.data[CONF_FOLDER_ID]),
|
||||
folder_name,
|
||||
)
|
||||
except NotFoundError:
|
||||
_LOGGER.debug("Creating backup folder %s", folder_name)
|
||||
backup_folder = await _handle_item_operation(
|
||||
lambda: client.create_folder(parent_id=approot.id, name=folder_name),
|
||||
folder_name,
|
||||
)
|
||||
hass.config_entries.async_update_entry(
|
||||
entry, data={**entry.data, CONF_FOLDER_ID: backup_folder.id}
|
||||
)
|
||||
|
||||
# write instance id to description
|
||||
if backup_folder.description != (instance_id := await async_get_instance_id(hass)):
|
||||
await _handle_item_operation(
|
||||
lambda: client.update_drive_item(
|
||||
backup_folder.id, ItemUpdate(description=instance_id)
|
||||
),
|
||||
folder_name,
|
||||
)
|
||||
|
||||
# update in case folder was renamed manually inside OneDrive
|
||||
if backup_folder.name != entry.data[CONF_FOLDER_NAME]:
|
||||
hass.config_entries.async_update_entry(
|
||||
entry, data={**entry.data, CONF_FOLDER_NAME: backup_folder.name}
|
||||
)
|
||||
except (HttpRequestException, OneDriveException, TimeoutError) as err:
|
||||
_LOGGER.debug("Failed to create backup folder", exc_info=True)
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="failed_to_get_folder",
|
||||
translation_placeholders={"folder": backup_folder_name},
|
||||
) from err
|
||||
|
||||
coordinator = OneDriveUpdateCoordinator(hass, entry, client)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
@ -152,3 +158,47 @@ async def _migrate_backup_files(client: OneDriveClient, backup_folder_id: str) -
|
||||
data=ItemUpdate(description=""),
|
||||
)
|
||||
_LOGGER.debug("Migrated backup file %s", file.name)
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, entry: OneDriveConfigEntry) -> bool:
|
||||
"""Migrate old entry."""
|
||||
if entry.version > 1:
|
||||
# This means the user has downgraded from a future version
|
||||
return False
|
||||
|
||||
if (version := entry.version) == 1 and (minor_version := entry.minor_version) == 1:
|
||||
_LOGGER.debug(
|
||||
"Migrating OneDrive config entry from version %s.%s", version, minor_version
|
||||
)
|
||||
|
||||
instance_id = await async_get_instance_id(hass)
|
||||
hass.config_entries.async_update_entry(
|
||||
entry,
|
||||
data={
|
||||
**entry.data,
|
||||
CONF_FOLDER_ID: "id", # will be updated during setup_entry
|
||||
CONF_FOLDER_NAME: f"backups_{instance_id[:8]}",
|
||||
},
|
||||
)
|
||||
_LOGGER.debug("Migration to version 1.2 successful")
|
||||
return True
|
||||
|
||||
|
||||
async def _handle_item_operation(
|
||||
func: Callable[[], Awaitable[Item]], folder: str
|
||||
) -> Item:
|
||||
try:
|
||||
return await func()
|
||||
except NotFoundError:
|
||||
raise
|
||||
except AuthenticationError as err:
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN, translation_key="authentication_failed"
|
||||
) from err
|
||||
except (OneDriveException, TimeoutError) as err:
|
||||
_LOGGER.debug("Failed to get approot", exc_info=True)
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="failed_to_get_folder",
|
||||
translation_placeholders={"folder": folder},
|
||||
) from err
|
||||
|
@ -74,7 +74,7 @@ def async_register_backup_agents_listener(
|
||||
def handle_backup_errors[_R, **P](
|
||||
func: Callable[Concatenate[OneDriveBackupAgent, P], Coroutine[Any, Any, _R]],
|
||||
) -> Callable[Concatenate[OneDriveBackupAgent, P], Coroutine[Any, Any, _R]]:
|
||||
"""Handle backup errors with a specific translation key."""
|
||||
"""Handle backup errors."""
|
||||
|
||||
@wraps(func)
|
||||
async def wrapper(
|
||||
|
@ -8,22 +8,47 @@ from typing import Any, cast
|
||||
|
||||
from onedrive_personal_sdk.clients.client import OneDriveClient
|
||||
from onedrive_personal_sdk.exceptions import OneDriveException
|
||||
from onedrive_personal_sdk.models.items import AppRoot, ItemUpdate
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlowResult, OptionsFlow
|
||||
from homeassistant.config_entries import (
|
||||
SOURCE_REAUTH,
|
||||
SOURCE_RECONFIGURE,
|
||||
SOURCE_USER,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
)
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.config_entry_oauth2_flow import AbstractOAuth2FlowHandler
|
||||
from homeassistant.helpers.instance_id import async_get as async_get_instance_id
|
||||
|
||||
from .const import CONF_DELETE_PERMANENTLY, DOMAIN, OAUTH_SCOPES
|
||||
from .const import (
|
||||
CONF_DELETE_PERMANENTLY,
|
||||
CONF_FOLDER_ID,
|
||||
CONF_FOLDER_NAME,
|
||||
DOMAIN,
|
||||
OAUTH_SCOPES,
|
||||
)
|
||||
from .coordinator import OneDriveConfigEntry
|
||||
|
||||
FOLDER_NAME_SCHEMA = vol.Schema({vol.Required(CONF_FOLDER_NAME): str})
|
||||
|
||||
|
||||
class OneDriveConfigFlow(AbstractOAuth2FlowHandler, domain=DOMAIN):
|
||||
"""Config flow to handle OneDrive OAuth2 authentication."""
|
||||
|
||||
DOMAIN = DOMAIN
|
||||
MINOR_VERSION = 2
|
||||
|
||||
client: OneDriveClient
|
||||
approot: AppRoot
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the OneDrive config flow."""
|
||||
super().__init__()
|
||||
self.step_data: dict[str, Any] = {}
|
||||
|
||||
@property
|
||||
def logger(self) -> logging.Logger:
|
||||
@ -35,6 +60,15 @@ class OneDriveConfigFlow(AbstractOAuth2FlowHandler, domain=DOMAIN):
|
||||
"""Extra data that needs to be appended to the authorize url."""
|
||||
return {"scope": " ".join(OAUTH_SCOPES)}
|
||||
|
||||
@property
|
||||
def apps_folder(self) -> str:
|
||||
"""Return the name of the Apps folder (translated)."""
|
||||
return (
|
||||
path.split("/")[-1]
|
||||
if (path := self.approot.parent_reference.path)
|
||||
else "Apps"
|
||||
)
|
||||
|
||||
async def async_oauth_create_entry(
|
||||
self,
|
||||
data: dict[str, Any],
|
||||
@ -44,12 +78,12 @@ class OneDriveConfigFlow(AbstractOAuth2FlowHandler, domain=DOMAIN):
|
||||
async def get_access_token() -> str:
|
||||
return cast(str, data[CONF_TOKEN][CONF_ACCESS_TOKEN])
|
||||
|
||||
graph_client = OneDriveClient(
|
||||
self.client = OneDriveClient(
|
||||
get_access_token, async_get_clientsession(self.hass)
|
||||
)
|
||||
|
||||
try:
|
||||
approot = await graph_client.get_approot()
|
||||
self.approot = await self.client.get_approot()
|
||||
except OneDriveException:
|
||||
self.logger.exception("Failed to connect to OneDrive")
|
||||
return self.async_abort(reason="connection_error")
|
||||
@ -57,26 +91,118 @@ class OneDriveConfigFlow(AbstractOAuth2FlowHandler, domain=DOMAIN):
|
||||
self.logger.exception("Unknown error")
|
||||
return self.async_abort(reason="unknown")
|
||||
|
||||
await self.async_set_unique_id(approot.parent_reference.drive_id)
|
||||
await self.async_set_unique_id(self.approot.parent_reference.drive_id)
|
||||
|
||||
if self.source == SOURCE_REAUTH:
|
||||
reauth_entry = self._get_reauth_entry()
|
||||
if self.source != SOURCE_USER:
|
||||
self._abort_if_unique_id_mismatch(
|
||||
reason="wrong_drive",
|
||||
)
|
||||
|
||||
if self.source == SOURCE_REAUTH:
|
||||
reauth_entry = self._get_reauth_entry()
|
||||
return self.async_update_reload_and_abort(
|
||||
entry=reauth_entry,
|
||||
data=data,
|
||||
)
|
||||
|
||||
if self.source != SOURCE_RECONFIGURE:
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
self.step_data = data
|
||||
|
||||
if self.source == SOURCE_RECONFIGURE:
|
||||
return await self.async_step_reconfigure_folder()
|
||||
|
||||
return await self.async_step_folder_name()
|
||||
|
||||
async def async_step_folder_name(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Step to ask for the folder name."""
|
||||
errors: dict[str, str] = {}
|
||||
instance_id = await async_get_instance_id(self.hass)
|
||||
if user_input is not None:
|
||||
try:
|
||||
folder = await self.client.create_folder(
|
||||
self.approot.id, user_input[CONF_FOLDER_NAME]
|
||||
)
|
||||
except OneDriveException:
|
||||
self.logger.debug("Failed to create folder", exc_info=True)
|
||||
errors["base"] = "folder_creation_error"
|
||||
else:
|
||||
if folder.description and folder.description != instance_id:
|
||||
errors[CONF_FOLDER_NAME] = "folder_already_in_use"
|
||||
if not errors:
|
||||
title = (
|
||||
f"{approot.created_by.user.display_name}'s OneDrive"
|
||||
if approot.created_by.user and approot.created_by.user.display_name
|
||||
f"{self.approot.created_by.user.display_name}'s OneDrive"
|
||||
if self.approot.created_by.user
|
||||
and self.approot.created_by.user.display_name
|
||||
else "OneDrive"
|
||||
)
|
||||
return self.async_create_entry(title=title, data=data)
|
||||
return self.async_create_entry(
|
||||
title=title,
|
||||
data={
|
||||
**self.step_data,
|
||||
CONF_FOLDER_ID: folder.id,
|
||||
CONF_FOLDER_NAME: user_input[CONF_FOLDER_NAME],
|
||||
},
|
||||
)
|
||||
|
||||
default_folder_name = (
|
||||
f"backups_{instance_id[:8]}"
|
||||
if user_input is None
|
||||
else user_input[CONF_FOLDER_NAME]
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="folder_name",
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
FOLDER_NAME_SCHEMA, {CONF_FOLDER_NAME: default_folder_name}
|
||||
),
|
||||
description_placeholders={
|
||||
"apps_folder": self.apps_folder,
|
||||
"approot": self.approot.name,
|
||||
},
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_reconfigure_folder(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Reconfigure the folder name."""
|
||||
errors: dict[str, str] = {}
|
||||
reconfigure_entry = self._get_reconfigure_entry()
|
||||
|
||||
if user_input is not None:
|
||||
if (
|
||||
new_folder_name := user_input[CONF_FOLDER_NAME]
|
||||
) != reconfigure_entry.data[CONF_FOLDER_NAME]:
|
||||
try:
|
||||
await self.client.update_drive_item(
|
||||
reconfigure_entry.data[CONF_FOLDER_ID],
|
||||
ItemUpdate(name=new_folder_name),
|
||||
)
|
||||
except OneDriveException:
|
||||
self.logger.debug("Failed to update folder", exc_info=True)
|
||||
errors["base"] = "folder_rename_error"
|
||||
if not errors:
|
||||
return self.async_update_reload_and_abort(
|
||||
reconfigure_entry,
|
||||
data={**reconfigure_entry.data, CONF_FOLDER_NAME: new_folder_name},
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="reconfigure_folder",
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
FOLDER_NAME_SCHEMA,
|
||||
{CONF_FOLDER_NAME: reconfigure_entry.data[CONF_FOLDER_NAME]},
|
||||
),
|
||||
description_placeholders={
|
||||
"apps_folder": self.apps_folder,
|
||||
"approot": self.approot.name,
|
||||
},
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_reauth(
|
||||
self, entry_data: Mapping[str, Any]
|
||||
@ -92,6 +218,12 @@ class OneDriveConfigFlow(AbstractOAuth2FlowHandler, domain=DOMAIN):
|
||||
return self.async_show_form(step_id="reauth_confirm")
|
||||
return await self.async_step_user()
|
||||
|
||||
async def async_step_reconfigure(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Reconfigure the entry."""
|
||||
return await self.async_step_user()
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(
|
||||
|
@ -6,6 +6,8 @@ from typing import Final
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
DOMAIN: Final = "onedrive"
|
||||
CONF_FOLDER_NAME: Final = "folder_name"
|
||||
CONF_FOLDER_ID: Final = "folder_id"
|
||||
|
||||
CONF_DELETE_PERMANENTLY: Final = "delete_permanently"
|
||||
|
||||
|
@ -73,10 +73,7 @@ rules:
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
icon-translations: done
|
||||
reconfiguration-flow:
|
||||
status: exempt
|
||||
comment: |
|
||||
Nothing to reconfigure.
|
||||
reconfiguration-flow: done
|
||||
repair-issues: done
|
||||
stale-devices:
|
||||
status: exempt
|
||||
|
@ -7,6 +7,26 @@
|
||||
"reauth_confirm": {
|
||||
"title": "[%key:common::config_flow::title::reauth%]",
|
||||
"description": "The OneDrive integration needs to re-authenticate your account"
|
||||
},
|
||||
"folder_name": {
|
||||
"title": "Pick a folder name",
|
||||
"description": "This name will be used to create a folder that is specific for this Home Assistant instance. This folder will be created inside `{apps_folder}/{approot}`",
|
||||
"data": {
|
||||
"folder_name": "Folder name"
|
||||
},
|
||||
"data_description": {
|
||||
"folder_name": "Name of the folder"
|
||||
}
|
||||
},
|
||||
"reconfigure_folder": {
|
||||
"title": "Change the folder name",
|
||||
"description": "Rename the instance specific folder inside `{apps_folder}/{approot}`. This will only rename the folder (and does not select another folder), so make sure the new name is not already in use.",
|
||||
"data": {
|
||||
"folder_name": "[%key:component::onedrive::config::step::folder_name::data::folder_name%]"
|
||||
},
|
||||
"data_description": {
|
||||
"folder_name": "[%key:component::onedrive::config::step::folder_name::data_description::folder_name%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
@ -23,10 +43,16 @@
|
||||
"connection_error": "Failed to connect to OneDrive.",
|
||||
"wrong_drive": "New account does not contain previously configured OneDrive.",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||
},
|
||||
"error": {
|
||||
"folder_rename_error": "Failed to rename folder",
|
||||
"folder_creation_error": "Failed to create folder",
|
||||
"folder_already_in_use": "Folder already used for backups from another Home Assistant instance"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
|
@ -5,13 +5,28 @@ from json import dumps
|
||||
import time
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from onedrive_personal_sdk.const import DriveState, DriveType
|
||||
from onedrive_personal_sdk.models.items import (
|
||||
AppRoot,
|
||||
Drive,
|
||||
DriveQuota,
|
||||
Folder,
|
||||
IdentitySet,
|
||||
ItemParentReference,
|
||||
User,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.application_credentials import (
|
||||
ClientCredential,
|
||||
async_import_client_credential,
|
||||
)
|
||||
from homeassistant.components.onedrive.const import DOMAIN, OAUTH_SCOPES
|
||||
from homeassistant.components.onedrive.const import (
|
||||
CONF_FOLDER_ID,
|
||||
CONF_FOLDER_NAME,
|
||||
DOMAIN,
|
||||
OAUTH_SCOPES,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
@ -19,10 +34,9 @@ from .const import (
|
||||
BACKUP_METADATA,
|
||||
CLIENT_ID,
|
||||
CLIENT_SECRET,
|
||||
MOCK_APPROOT,
|
||||
IDENTITY_SET,
|
||||
INSTANCE_ID,
|
||||
MOCK_BACKUP_FILE,
|
||||
MOCK_BACKUP_FOLDER,
|
||||
MOCK_DRIVE,
|
||||
MOCK_METADATA_FILE,
|
||||
)
|
||||
|
||||
@ -66,8 +80,11 @@ def mock_config_entry(expires_at: int, scopes: list[str]) -> MockConfigEntry:
|
||||
"expires_at": expires_at,
|
||||
"scope": " ".join(scopes),
|
||||
},
|
||||
CONF_FOLDER_NAME: "backups_123",
|
||||
CONF_FOLDER_ID: "my_folder_id",
|
||||
},
|
||||
unique_id="mock_drive_id",
|
||||
minor_version=2,
|
||||
)
|
||||
|
||||
|
||||
@ -87,14 +104,80 @@ def mock_onedrive_client_init() -> Generator[MagicMock]:
|
||||
yield onedrive_client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_approot() -> AppRoot:
|
||||
"""Return a mocked approot."""
|
||||
return AppRoot(
|
||||
id="id",
|
||||
child_count=0,
|
||||
size=0,
|
||||
name="name",
|
||||
parent_reference=ItemParentReference(
|
||||
drive_id="mock_drive_id", id="id", path="path"
|
||||
),
|
||||
created_by=IdentitySet(
|
||||
user=User(
|
||||
display_name="John Doe",
|
||||
id="id",
|
||||
email="john@doe.com",
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_drive() -> Drive:
|
||||
"""Return a mocked drive."""
|
||||
return Drive(
|
||||
id="mock_drive_id",
|
||||
name="My Drive",
|
||||
drive_type=DriveType.PERSONAL,
|
||||
owner=IDENTITY_SET,
|
||||
quota=DriveQuota(
|
||||
deleted=5,
|
||||
remaining=805306368,
|
||||
state=DriveState.NEARING,
|
||||
total=5368709120,
|
||||
used=4250000000,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_folder() -> Folder:
|
||||
"""Return a mocked backup folder."""
|
||||
return Folder(
|
||||
id="my_folder_id",
|
||||
name="name",
|
||||
size=0,
|
||||
child_count=0,
|
||||
description="9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0",
|
||||
parent_reference=ItemParentReference(
|
||||
drive_id="mock_drive_id", id="id", path="path"
|
||||
),
|
||||
created_by=IdentitySet(
|
||||
user=User(
|
||||
display_name="John Doe",
|
||||
id="id",
|
||||
email="john@doe.com",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_onedrive_client(mock_onedrive_client_init: MagicMock) -> Generator[MagicMock]:
|
||||
def mock_onedrive_client(
|
||||
mock_onedrive_client_init: MagicMock,
|
||||
mock_approot: AppRoot,
|
||||
mock_drive: Drive,
|
||||
mock_folder: Folder,
|
||||
) -> Generator[MagicMock]:
|
||||
"""Return a mocked GraphServiceClient."""
|
||||
client = mock_onedrive_client_init.return_value
|
||||
client.get_approot.return_value = MOCK_APPROOT
|
||||
client.create_folder.return_value = MOCK_BACKUP_FOLDER
|
||||
client.get_approot.return_value = mock_approot
|
||||
client.create_folder.return_value = mock_folder
|
||||
client.list_drive_items.return_value = [MOCK_BACKUP_FILE, MOCK_METADATA_FILE]
|
||||
client.get_drive_item.return_value = MOCK_BACKUP_FILE
|
||||
client.get_drive_item.return_value = mock_folder
|
||||
client.upload_file.return_value = MOCK_METADATA_FILE
|
||||
|
||||
class MockStreamReader:
|
||||
@ -105,7 +188,7 @@ def mock_onedrive_client(mock_onedrive_client_init: MagicMock) -> Generator[Magi
|
||||
return dumps(BACKUP_METADATA).encode()
|
||||
|
||||
client.download_drive_item.return_value = MockStreamReader()
|
||||
client.get_drive.return_value = MOCK_DRIVE
|
||||
client.get_drive.return_value = mock_drive
|
||||
return client
|
||||
|
||||
|
||||
@ -131,8 +214,14 @@ def mock_setup_entry() -> Generator[AsyncMock]:
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_instance_id() -> Generator[AsyncMock]:
|
||||
"""Mock the instance ID."""
|
||||
with patch(
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.onedrive.async_get_instance_id",
|
||||
return_value="9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0",
|
||||
return_value=INSTANCE_ID,
|
||||
) as mock_instance_id,
|
||||
patch(
|
||||
"homeassistant.components.onedrive.config_flow.async_get_instance_id",
|
||||
new=mock_instance_id,
|
||||
),
|
||||
):
|
||||
yield
|
||||
|
@ -3,13 +3,8 @@
|
||||
from html import escape
|
||||
from json import dumps
|
||||
|
||||
from onedrive_personal_sdk.const import DriveState, DriveType
|
||||
from onedrive_personal_sdk.models.items import (
|
||||
AppRoot,
|
||||
Drive,
|
||||
DriveQuota,
|
||||
File,
|
||||
Folder,
|
||||
Hashes,
|
||||
IdentitySet,
|
||||
ItemParentReference,
|
||||
@ -34,6 +29,8 @@ BACKUP_METADATA = {
|
||||
"size": 34519040,
|
||||
}
|
||||
|
||||
INSTANCE_ID = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0"
|
||||
|
||||
IDENTITY_SET = IdentitySet(
|
||||
user=User(
|
||||
display_name="John Doe",
|
||||
@ -42,28 +39,6 @@ IDENTITY_SET = IdentitySet(
|
||||
)
|
||||
)
|
||||
|
||||
MOCK_APPROOT = AppRoot(
|
||||
id="id",
|
||||
child_count=0,
|
||||
size=0,
|
||||
name="name",
|
||||
parent_reference=ItemParentReference(
|
||||
drive_id="mock_drive_id", id="id", path="path"
|
||||
),
|
||||
created_by=IDENTITY_SET,
|
||||
)
|
||||
|
||||
MOCK_BACKUP_FOLDER = Folder(
|
||||
id="id",
|
||||
name="name",
|
||||
size=0,
|
||||
child_count=0,
|
||||
parent_reference=ItemParentReference(
|
||||
drive_id="mock_drive_id", id="id", path="path"
|
||||
),
|
||||
created_by=IDENTITY_SET,
|
||||
)
|
||||
|
||||
MOCK_BACKUP_FILE = File(
|
||||
id="id",
|
||||
name="23e64aec.tar",
|
||||
@ -75,7 +50,6 @@ MOCK_BACKUP_FILE = File(
|
||||
quick_xor_hash="hash",
|
||||
),
|
||||
mime_type="application/x-tar",
|
||||
description="",
|
||||
created_by=IDENTITY_SET,
|
||||
)
|
||||
|
||||
@ -101,18 +75,3 @@ MOCK_METADATA_FILE = File(
|
||||
),
|
||||
created_by=IDENTITY_SET,
|
||||
)
|
||||
|
||||
|
||||
MOCK_DRIVE = Drive(
|
||||
id="mock_drive_id",
|
||||
name="My Drive",
|
||||
drive_type=DriveType.PERSONAL,
|
||||
owner=IDENTITY_SET,
|
||||
quota=DriveQuota(
|
||||
deleted=5,
|
||||
remaining=805306368,
|
||||
state=DriveState.NEARING,
|
||||
total=5368709120,
|
||||
used=4250000000,
|
||||
),
|
||||
)
|
||||
|
@ -4,11 +4,14 @@ from http import HTTPStatus
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from onedrive_personal_sdk.exceptions import OneDriveException
|
||||
from onedrive_personal_sdk.models.items import AppRoot, Folder, ItemUpdate
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.onedrive.const import (
|
||||
CONF_DELETE_PERMANENTLY,
|
||||
CONF_FOLDER_ID,
|
||||
CONF_FOLDER_NAME,
|
||||
DOMAIN,
|
||||
OAUTH2_AUTHORIZE,
|
||||
OAUTH2_TOKEN,
|
||||
@ -20,7 +23,7 @@ from homeassistant.data_entry_flow import FlowResultType
|
||||
from homeassistant.helpers import config_entry_oauth2_flow
|
||||
|
||||
from . import setup_integration
|
||||
from .const import CLIENT_ID, MOCK_APPROOT
|
||||
from .const import CLIENT_ID
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
@ -85,6 +88,11 @@ async def test_full_flow(
|
||||
token_callback = mock_onedrive_client_init.call_args[0][0]
|
||||
assert await token_callback() == "mock-access-token"
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_FOLDER_NAME: "myFolder"}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
@ -92,6 +100,8 @@ async def test_full_flow(
|
||||
assert result["result"].unique_id == "mock_drive_id"
|
||||
assert result["data"][CONF_TOKEN][CONF_ACCESS_TOKEN] == "mock-access-token"
|
||||
assert result["data"][CONF_TOKEN]["refresh_token"] == "mock-refresh-token"
|
||||
assert result["data"][CONF_FOLDER_NAME] == "myFolder"
|
||||
assert result["data"][CONF_FOLDER_ID] == "my_folder_id"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("current_request_with_host")
|
||||
@ -101,10 +111,11 @@ async def test_full_flow_with_owner_not_found(
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
mock_setup_entry: AsyncMock,
|
||||
mock_onedrive_client: MagicMock,
|
||||
mock_approot: MagicMock,
|
||||
) -> None:
|
||||
"""Ensure we get a default title if the drive's owner can't be read."""
|
||||
|
||||
mock_onedrive_client.get_approot.return_value.created_by.user = None
|
||||
mock_approot.created_by.user = None
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
@ -112,6 +123,11 @@ async def test_full_flow_with_owner_not_found(
|
||||
await _do_get_token(hass, result, hass_client_no_auth, aioclient_mock)
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_FOLDER_NAME: "myFolder"}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
@ -119,6 +135,94 @@ async def test_full_flow_with_owner_not_found(
|
||||
assert result["result"].unique_id == "mock_drive_id"
|
||||
assert result["data"][CONF_TOKEN][CONF_ACCESS_TOKEN] == "mock-access-token"
|
||||
assert result["data"][CONF_TOKEN]["refresh_token"] == "mock-refresh-token"
|
||||
assert result["data"][CONF_FOLDER_NAME] == "myFolder"
|
||||
assert result["data"][CONF_FOLDER_ID] == "my_folder_id"
|
||||
|
||||
mock_onedrive_client.reset_mock()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("current_request_with_host")
|
||||
async def test_folder_already_in_use(
|
||||
hass: HomeAssistant,
|
||||
hass_client_no_auth: ClientSessionGenerator,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
mock_setup_entry: AsyncMock,
|
||||
mock_onedrive_client: MagicMock,
|
||||
mock_instance_id: AsyncMock,
|
||||
mock_folder: Folder,
|
||||
) -> None:
|
||||
"""Ensure a folder that is already in use is not allowed."""
|
||||
|
||||
mock_folder.description = "1234"
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
await _do_get_token(hass, result, hass_client_no_auth, aioclient_mock)
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_FOLDER_NAME: "myFolder"}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {CONF_FOLDER_NAME: "folder_already_in_use"}
|
||||
|
||||
# clear error and try again
|
||||
mock_onedrive_client.create_folder.return_value.description = mock_instance_id
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_FOLDER_NAME: "myFolder"}
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "John Doe's OneDrive"
|
||||
assert result["result"].unique_id == "mock_drive_id"
|
||||
assert result["data"][CONF_TOKEN][CONF_ACCESS_TOKEN] == "mock-access-token"
|
||||
assert result["data"][CONF_TOKEN]["refresh_token"] == "mock-refresh-token"
|
||||
assert result["data"][CONF_FOLDER_NAME] == "myFolder"
|
||||
assert result["data"][CONF_FOLDER_ID] == "my_folder_id"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("current_request_with_host")
|
||||
async def test_error_during_folder_creation(
|
||||
hass: HomeAssistant,
|
||||
hass_client_no_auth: ClientSessionGenerator,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
mock_setup_entry: AsyncMock,
|
||||
mock_onedrive_client: MagicMock,
|
||||
) -> None:
|
||||
"""Ensure we can create the backup folder."""
|
||||
|
||||
mock_onedrive_client.create_folder.side_effect = OneDriveException()
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
await _do_get_token(hass, result, hass_client_no_auth, aioclient_mock)
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_FOLDER_NAME: "myFolder"}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {"base": "folder_creation_error"}
|
||||
|
||||
mock_onedrive_client.create_folder.side_effect = None
|
||||
|
||||
# clear error and try again
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_FOLDER_NAME: "myFolder"}
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "John Doe's OneDrive"
|
||||
assert result["result"].unique_id == "mock_drive_id"
|
||||
assert result["data"][CONF_TOKEN][CONF_ACCESS_TOKEN] == "mock-access-token"
|
||||
assert result["data"][CONF_TOKEN]["refresh_token"] == "mock-refresh-token"
|
||||
assert result["data"][CONF_FOLDER_NAME] == "myFolder"
|
||||
assert result["data"][CONF_FOLDER_ID] == "my_folder_id"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("current_request_with_host")
|
||||
@ -205,11 +309,11 @@ async def test_reauth_flow_id_changed(
|
||||
mock_setup_entry: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_onedrive_client: MagicMock,
|
||||
mock_approot: AppRoot,
|
||||
) -> None:
|
||||
"""Test that the reauth flow fails on a different drive id."""
|
||||
app_root = MOCK_APPROOT
|
||||
app_root.parent_reference.drive_id = "other_drive_id"
|
||||
mock_onedrive_client.get_approot.return_value = app_root
|
||||
|
||||
mock_approot.parent_reference.drive_id = "other_drive_id"
|
||||
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
@ -226,6 +330,104 @@ async def test_reauth_flow_id_changed(
|
||||
assert result["reason"] == "wrong_drive"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("current_request_with_host")
|
||||
async def test_reconfigure_flow(
|
||||
hass: HomeAssistant,
|
||||
hass_client_no_auth: ClientSessionGenerator,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
mock_onedrive_client: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_setup_entry: AsyncMock,
|
||||
) -> None:
|
||||
"""Testing reconfgure flow."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
result = await mock_config_entry.start_reconfigure_flow(hass)
|
||||
await _do_get_token(hass, result, hass_client_no_auth, aioclient_mock)
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "reconfigure_folder"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_FOLDER_NAME: "newFolder"}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
mock_onedrive_client.update_drive_item.assert_called_once_with(
|
||||
mock_config_entry.data[CONF_FOLDER_ID], ItemUpdate(name="newFolder")
|
||||
)
|
||||
assert mock_config_entry.data[CONF_FOLDER_NAME] == "newFolder"
|
||||
assert mock_config_entry.data[CONF_TOKEN][CONF_ACCESS_TOKEN] == "mock-access-token"
|
||||
assert mock_config_entry.data[CONF_TOKEN]["refresh_token"] == "mock-refresh-token"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("current_request_with_host")
|
||||
async def test_reconfigure_flow_error(
|
||||
hass: HomeAssistant,
|
||||
hass_client_no_auth: ClientSessionGenerator,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
mock_onedrive_client: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_setup_entry: AsyncMock,
|
||||
) -> None:
|
||||
"""Testing reconfgure flow errors."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
result = await mock_config_entry.start_reconfigure_flow(hass)
|
||||
await _do_get_token(hass, result, hass_client_no_auth, aioclient_mock)
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "reconfigure_folder"
|
||||
|
||||
mock_onedrive_client.update_drive_item.side_effect = OneDriveException()
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_FOLDER_NAME: "newFolder"}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "reconfigure_folder"
|
||||
assert result["errors"] == {"base": "folder_rename_error"}
|
||||
|
||||
# clear side effect
|
||||
mock_onedrive_client.update_drive_item.side_effect = None
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_FOLDER_NAME: "newFolder"}
|
||||
)
|
||||
|
||||
assert mock_config_entry.data[CONF_FOLDER_NAME] == "newFolder"
|
||||
assert mock_config_entry.data[CONF_TOKEN][CONF_ACCESS_TOKEN] == "mock-access-token"
|
||||
assert mock_config_entry.data[CONF_TOKEN]["refresh_token"] == "mock-refresh-token"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("current_request_with_host")
|
||||
async def test_reconfigure_flow_id_changed(
|
||||
hass: HomeAssistant,
|
||||
hass_client_no_auth: ClientSessionGenerator,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
mock_setup_entry: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_onedrive_client: MagicMock,
|
||||
mock_approot: AppRoot,
|
||||
) -> None:
|
||||
"""Test that the reconfigure flow fails on a different drive id."""
|
||||
|
||||
mock_approot.parent_reference.drive_id = "other_drive_id"
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
result = await mock_config_entry.start_reconfigure_flow(hass)
|
||||
await _do_get_token(hass, result, hass_client_no_auth, aioclient_mock)
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "wrong_drive"
|
||||
|
||||
|
||||
async def test_options_flow(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
|
@ -1,22 +1,31 @@
|
||||
"""Test the OneDrive setup."""
|
||||
|
||||
from copy import deepcopy
|
||||
from copy import copy
|
||||
from html import escape
|
||||
from json import dumps
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from onedrive_personal_sdk.const import DriveState
|
||||
from onedrive_personal_sdk.exceptions import AuthenticationError, OneDriveException
|
||||
from onedrive_personal_sdk.exceptions import (
|
||||
AuthenticationError,
|
||||
NotFoundError,
|
||||
OneDriveException,
|
||||
)
|
||||
from onedrive_personal_sdk.models.items import AppRoot, Drive, Folder, ItemUpdate
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.onedrive.const import DOMAIN
|
||||
from homeassistant.components.onedrive.const import (
|
||||
CONF_FOLDER_ID,
|
||||
CONF_FOLDER_NAME,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, issue_registry as ir
|
||||
|
||||
from . import setup_integration
|
||||
from .const import BACKUP_METADATA, MOCK_BACKUP_FILE, MOCK_DRIVE
|
||||
from .const import BACKUP_METADATA, INSTANCE_ID, MOCK_BACKUP_FILE
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@ -72,11 +81,64 @@ async def test_get_integration_folder_error(
|
||||
mock_onedrive_client: MagicMock,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test faulty approot retrieval."""
|
||||
mock_onedrive_client.create_folder.side_effect = OneDriveException()
|
||||
"""Test faulty integration folder retrieval."""
|
||||
mock_onedrive_client.get_drive_item.side_effect = OneDriveException()
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
assert "Failed to get backups_9f86d081 folder" in caplog.text
|
||||
assert "Failed to get backups_123 folder" in caplog.text
|
||||
|
||||
|
||||
async def test_get_integration_folder_creation(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_onedrive_client: MagicMock,
|
||||
mock_approot: AppRoot,
|
||||
mock_folder: Folder,
|
||||
) -> None:
|
||||
"""Test faulty integration folder creation."""
|
||||
folder_name = copy(mock_config_entry.data[CONF_FOLDER_NAME])
|
||||
mock_onedrive_client.get_drive_item.side_effect = NotFoundError(404, "Not found")
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||
mock_onedrive_client.create_folder.assert_called_once_with(
|
||||
parent_id=mock_approot.id,
|
||||
name=folder_name,
|
||||
)
|
||||
# ensure the folder id and name are updated
|
||||
assert mock_config_entry.data[CONF_FOLDER_ID] == mock_folder.id
|
||||
assert mock_config_entry.data[CONF_FOLDER_NAME] == mock_folder.name
|
||||
|
||||
|
||||
async def test_get_integration_folder_creation_error(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_onedrive_client: MagicMock,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test faulty integration folder creation error."""
|
||||
mock_onedrive_client.get_drive_item.side_effect = NotFoundError(404, "Not found")
|
||||
mock_onedrive_client.create_folder.side_effect = OneDriveException()
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
assert "Failed to get backups_123 folder" in caplog.text
|
||||
|
||||
|
||||
async def test_update_instance_id_description(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_onedrive_client: MagicMock,
|
||||
mock_folder: Folder,
|
||||
) -> None:
|
||||
"""Test we write the instance id to the folder."""
|
||||
mock_folder.description = ""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_onedrive_client.update_drive_item.assert_called_with(
|
||||
mock_folder.id, ItemUpdate(description=INSTANCE_ID)
|
||||
)
|
||||
|
||||
|
||||
async def test_migrate_metadata_files(
|
||||
@ -125,12 +187,13 @@ async def test_device(
|
||||
mock_config_entry: MockConfigEntry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_drive: Drive,
|
||||
) -> None:
|
||||
"""Test the device."""
|
||||
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
device = device_registry.async_get_device({(DOMAIN, MOCK_DRIVE.id)})
|
||||
device = device_registry.async_get_device({(DOMAIN, mock_drive.id)})
|
||||
assert device
|
||||
assert device == snapshot
|
||||
|
||||
@ -154,17 +217,62 @@ async def test_data_cap_issues(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_onedrive_client: MagicMock,
|
||||
mock_drive: Drive,
|
||||
drive_state: DriveState,
|
||||
issue_key: str,
|
||||
issue_exists: bool,
|
||||
) -> None:
|
||||
"""Make sure we get issues for high data usage."""
|
||||
mock_drive = deepcopy(MOCK_DRIVE)
|
||||
assert mock_drive.quota
|
||||
mock_drive.quota.state = drive_state
|
||||
mock_onedrive_client.get_drive.return_value = mock_drive
|
||||
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
issue_registry = ir.async_get(hass)
|
||||
issue = issue_registry.async_get_issue(DOMAIN, issue_key)
|
||||
assert (issue is not None) == issue_exists
|
||||
|
||||
|
||||
async def test_1_1_to_1_2_migration(
|
||||
hass: HomeAssistant,
|
||||
mock_onedrive_client: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_folder: Folder,
|
||||
) -> None:
|
||||
"""Test migration from 1.1 to 1.2."""
|
||||
old_config_entry = MockConfigEntry(
|
||||
unique_id="mock_drive_id",
|
||||
title="John Doe's OneDrive",
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
"auth_implementation": mock_config_entry.data["auth_implementation"],
|
||||
"token": mock_config_entry.data["token"],
|
||||
},
|
||||
)
|
||||
|
||||
# will always 404 after migration, because of dummy id
|
||||
mock_onedrive_client.get_drive_item.side_effect = NotFoundError(404, "Not found")
|
||||
|
||||
await setup_integration(hass, old_config_entry)
|
||||
assert old_config_entry.data[CONF_FOLDER_ID] == mock_folder.id
|
||||
assert old_config_entry.data[CONF_FOLDER_NAME] == mock_folder.name
|
||||
|
||||
|
||||
async def test_migration_guard_against_major_downgrade(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test migration guards against major downgrades."""
|
||||
old_config_entry = MockConfigEntry(
|
||||
unique_id="mock_drive_id",
|
||||
title="John Doe's OneDrive",
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
"auth_implementation": mock_config_entry.data["auth_implementation"],
|
||||
"token": mock_config_entry.data["token"],
|
||||
},
|
||||
version=2,
|
||||
)
|
||||
|
||||
await setup_integration(hass, old_config_entry)
|
||||
assert old_config_entry.state is ConfigEntryState.MIGRATION_ERROR
|
||||
|
Loading…
x
Reference in New Issue
Block a user